@sonnechasser/ntrp 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8279,6 +8279,736 @@ var init_playbook = __esm({
8279
8279
  }
8280
8280
  });
8281
8281
 
8282
+ // src/workflows/registry.ts
8283
+ function parseFrontmatter(raw) {
8284
+ if (!raw.startsWith("---")) return { meta: {}, body: raw };
8285
+ const end = raw.indexOf("\n---", 3);
8286
+ if (end === -1) return { meta: {}, body: raw };
8287
+ const fm = raw.slice(3, end).trim();
8288
+ const body = raw.slice(end + 4).replace(/^\r?\n/, "");
8289
+ const meta = {};
8290
+ for (const line of fm.split("\n")) {
8291
+ const match = line.match(/^(\w+):\s*(.*)$/);
8292
+ if (!match) continue;
8293
+ let value = match[2].trim();
8294
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
8295
+ value = value.slice(1, -1);
8296
+ }
8297
+ meta[match[1]] = value;
8298
+ }
8299
+ return { meta, body: body.trim() };
8300
+ }
8301
+ function loadRegistry() {
8302
+ if (registry) return registry;
8303
+ registry = /* @__PURE__ */ new Map();
8304
+ for (const entry of EMBEDDED_WORKFLOWS) {
8305
+ const { meta: fm, body } = parseFrontmatter(entry.raw);
8306
+ const meta = {
8307
+ name: fm.name ?? entry.name,
8308
+ description: fm.description ?? "",
8309
+ section: fm.section ?? "Other",
8310
+ args: fm.args,
8311
+ handler: fm.handler ?? "",
8312
+ body,
8313
+ hidden: fm.hidden === "true"
8314
+ };
8315
+ registry.set(meta.name, { meta, handler: null });
8316
+ }
8317
+ return registry;
8318
+ }
8319
+ function listWorkflows(includeHidden = false) {
8320
+ const all2 = Array.from(loadRegistry().values()).map((e) => e.meta);
8321
+ return includeHidden ? all2 : all2.filter((m) => !m.hidden);
8322
+ }
8323
+ var registry, EMBEDDED_WORKFLOWS;
8324
+ var init_registry = __esm({
8325
+ "src/workflows/registry.ts"() {
8326
+ "use strict";
8327
+ registry = null;
8328
+ EMBEDDED_WORKFLOWS = [
8329
+ {
8330
+ name: "new",
8331
+ raw: `---
8332
+ name: new
8333
+ description: Start a new analysis \u2014 one menu picks data + first report
8334
+ section: Hidden
8335
+ hidden: true
8336
+ args: [<file.csv>] | --demo [--scenario <name>] | --empty [--lens health|metrics]
8337
+ handler: ../commands/new.ts
8338
+ ---
8339
+
8340
+ Start a fresh point-in-time analysis. Interactive mode uses **one menu**: demo \u2192
8341
+ health, demo \u2192 metrics, your CSV, or empty. Loads data and runs the first report
8342
+ (formulas only \u2014 no AI unless you add \`--findings\` later). Demo metrics works
8343
+ without \`/onboard\`; your own CSV needs a profile for metrics calibration.
8344
+ After the report, **ask questions in plain English** \u2014 no slash needed.`
8345
+ },
8346
+ {
8347
+ name: "end",
8348
+ raw: `---
8349
+ name: end
8350
+ description: Close the current analysis without a handoff
8351
+ section: Start
8352
+ args:
8353
+ handler: ../commands/end.ts
8354
+ ---
8355
+
8356
+ Mark the current session as finished even when you didn't produce a report or
8357
+ other output. It drops off the "in progress" list, saves your transcript and
8358
+ dataset anchor for later, and rotates you to a fresh empty session. Use
8359
+ \`/handoff\` instead when you want to ship something.`
8360
+ },
8361
+ {
8362
+ name: "session",
8363
+ raw: `---
8364
+ name: session
8365
+ description: Pick up or browse your analyses
8366
+ section: Hidden
8367
+ hidden: true
8368
+ args: [<id|name>] | new
8369
+ handler: ../commands/session.ts
8370
+ ---
8371
+
8372
+ Move between your point-in-time analyses. With no arguments, lists your
8373
+ sessions with unfinished work (reached insight, never delivered) surfaced
8374
+ first. Pass a session id (or type the 4-char suffix after listing) to pick it
8375
+ back up \u2014 this rebinds its dataset and conversation so you continue exactly
8376
+ where you left off. \`new\` starts a fresh analysis.`
8377
+ },
8378
+ {
8379
+ name: "handoff",
8380
+ raw: `---
8381
+ name: handoff
8382
+ description: Turn the analysis into an output
8383
+ section: Start
8384
+ args: [report|notes|csv|publish|prompt] [deck|asana|clay|plan]
8385
+ handler: ../commands/handoff.ts
8386
+ ---
8387
+
8388
+ Close the loop to action. Produce a markdown report, a notes export, CSV
8389
+ receipts, or a repository package \u2014 or generate a ready-to-paste prompt for
8390
+ another agent to build a review deck, an Asana project, a Clay table, or an
8391
+ action plan from this diagnosis. Producing an output marks the session
8392
+ delivered so it stops showing up as unfinished work.`
8393
+ },
8394
+ {
8395
+ name: "onboard",
8396
+ raw: `---
8397
+ name: onboard
8398
+ description: Set up your company profile
8399
+ section: Settings
8400
+ handler: ../commands/onboard.ts
8401
+ ---
8402
+
8403
+ Run the first-run wizard to build a rich company profile. Configures one or
8404
+ two LLM engines (Anthropic and/or OpenAI), then asks
8405
+ a few seed questions and uses AI to draft industry, ICP, deal size, and stack
8406
+ guesses. Profile is stored at \`~/.ntrp/profile.json\` and flows into every
8407
+ AI surface (findings, NL answers, demo generation).`
8408
+ },
8409
+ {
8410
+ name: "sessions",
8411
+ raw: `---
8412
+ name: sessions
8413
+ description: Browse past session history
8414
+ section: More
8415
+ args: [list|show <id>]
8416
+ handler: ../commands/sessions.ts
8417
+ hidden: true
8418
+ ---
8419
+
8420
+ List and inspect past REPL sessions. Shows session dates, AI-generated
8421
+ summaries, and exchange counts. Use \`show <id>\` to view the full
8422
+ conversation from a specific session.`
8423
+ },
8424
+ {
8425
+ name: "setup",
8426
+ raw: `---
8427
+ name: setup
8428
+ description: Configure NTRP for headless and agent use
8429
+ section: Settings
8430
+ args: check | agent [--profile <file|->]
8431
+ handler: ../commands/setup.ts
8432
+ ---
8433
+
8434
+ Validate local readiness or configure NTRP non-interactively for automation.
8435
+ \`setup check --json\` reports license, profile, API key, database, and writable
8436
+ directory state. \`setup agent\` accepts a profile JSON file or direct flags \u2014
8437
+ \`--llm-key <key>\` auto-detects the provider from any pasted key
8438
+ (\`--llm-provider <id>\` to force one).`
8439
+ },
8440
+ {
8441
+ name: "update",
8442
+ raw: `---
8443
+ name: update
8444
+ description: Update NTRP to the latest version
8445
+ section: Settings
8446
+ handler: ../commands/update.ts
8447
+ ---
8448
+
8449
+ Update the globally installed NTRP package via npm.`
8450
+ },
8451
+ {
8452
+ name: "resume",
8453
+ raw: `---
8454
+ name: resume
8455
+ description: Continue a previous session
8456
+ section: More
8457
+ args: [id]
8458
+ handler: ../commands/resume.ts
8459
+ hidden: true
8460
+ ---
8461
+
8462
+ Load a previous session's context so the AI can reference what was
8463
+ discussed before. Without an ID, resumes the most recent session.
8464
+ Use a full session ID or 4-char suffix.`
8465
+ },
8466
+ {
8467
+ name: "name",
8468
+ raw: `---
8469
+ name: name
8470
+ description: Tag this session with a label
8471
+ section: More
8472
+ args: [label]
8473
+ handler: ../commands/name.ts
8474
+ ---
8475
+
8476
+ Give the current session a human-readable name so you can find it
8477
+ later. The name appears in the REPL prompt, session list, and
8478
+ welcome dashboard. Max 40 characters.`
8479
+ },
8480
+ {
8481
+ name: "switch",
8482
+ raw: `---
8483
+ name: switch
8484
+ description: Jump to a named session
8485
+ section: More
8486
+ args: [name]
8487
+ handler: ../commands/switch.ts
8488
+ hidden: true
8489
+ ---
8490
+
8491
+ Save the current session and switch to a named one. If the name
8492
+ exists, loads its context and messages. If new, creates a fresh
8493
+ session with that name. Without arguments, lists all named sessions.`
8494
+ },
8495
+ {
8496
+ name: "actions",
8497
+ raw: `---
8498
+ name: actions
8499
+ description: Propose, approve, and execute actions
8500
+ section: More
8501
+ args: [list|test|show|approve|reject|execute|continue] [id]
8502
+ handler: ../commands/actions.ts
8503
+ ---
8504
+
8505
+ Create and manage action proposals. \`/actions test\` creates a local manual
8506
+ dry-run proposal that exercises the approval and execution lifecycle without
8507
+ touching external tools. Execute-class actions require local approval before
8508
+ they can run. Use \`/actions continue\` to advance the newest pending or
8509
+ approved proposal without copying a handle during the active workflow.`
8510
+ },
8511
+ {
8512
+ name: "diagnose",
8513
+ raw: `---
8514
+ name: diagnose
8515
+ description: Compute vital signs and generate findings
8516
+ section: Hidden
8517
+ hidden: true
8518
+ args: [--deep] [--segment <name>]
8519
+ handler: ../commands/diagnose.ts
8520
+ ---
8521
+
8522
+ Compute the 5 vital signs (freshness, flow rate, drop rate, signal-to-noise,
8523
+ thread depth) for either the full dataset or a segment. Companion to \`/metrics\`
8524
+ when your session primary is SaaS metrics. Use \`--deep\` to run the agentic
8525
+ investigation loop instead of the single-shot findings path.`
8526
+ },
8527
+ {
8528
+ name: "metrics",
8529
+ raw: `---
8530
+ name: metrics
8531
+ description: SaaS metrics \u2014 refresh or add the revenue view
8532
+ section: Hidden
8533
+ hidden: true
8534
+ args: [--findings] [--segment <name>]
8535
+ handler: ../commands/metrics.ts
8536
+ ---
8537
+
8538
+ Compute SaaS revenue metrics from pipeline or revenue-ledger data: ARR, NRR/GRR,
8539
+ Win Rate, Pipeline Coverage, and more. Each metric includes a confidence score
8540
+ and reliability gate showing what data unlocks the next tier. Use \`--findings\`
8541
+ for AI analysis calibrated to your company profile. Revenue ledger CSV format:
8542
+ account, period, mrr, event_type.`
8543
+ },
8544
+ {
8545
+ name: "ask",
8546
+ raw: `---
8547
+ name: ask
8548
+ description: Chat with your pipeline data
8549
+ section: Hidden
8550
+ hidden: true
8551
+ args: <question>
8552
+ handler: ../commands/ask.ts
8553
+ ---
8554
+
8555
+ Ask a plain-English question about your GTM health and SaaS metrics. Free-form
8556
+ text at the REPL prompt routes to the same agent. Respects your session primary
8557
+ lens; can cross-reference vital signs and revenue metrics via tools.`
8558
+ },
8559
+ {
8560
+ name: "recap",
8561
+ raw: `---
8562
+ name: recap
8563
+ description: Summarize the current session
8564
+ section: More
8565
+ handler: ../commands/recap.ts
8566
+ ---
8567
+
8568
+ Summarize the current REPL session using AI. Reads all natural-language
8569
+ exchanges from the session and produces a structured overview: key findings,
8570
+ dollar impacts, and recommended next steps.`
8571
+ },
8572
+ {
8573
+ name: "remember",
8574
+ raw: `---
8575
+ name: remember
8576
+ description: Teach the analyst a durable fact
8577
+ section: More
8578
+ args: <fact> | decision: <text> | preference: <text>
8579
+ handler: ../commands/remember.ts
8580
+ ---
8581
+
8582
+ Store a durable fact, decision, or preference about your business. Stored
8583
+ memory flows into every future analysis so the agent gets to know your
8584
+ business better over time \u2014 like a consultant building up a client file.`
8585
+ },
8586
+ {
8587
+ name: "recall",
8588
+ raw: `---
8589
+ name: recall
8590
+ description: See what the analyst remembers
8591
+ section: More
8592
+ args: [topic]
8593
+ handler: ../commands/recall.ts
8594
+ ---
8595
+
8596
+ Jog the analyst's memory. With no arguments, lists the durable facts it knows
8597
+ and the analyses it has already run. Pass a topic to see what it remembers
8598
+ about that subject \u2014 pulled from facts, strategies, wins, and ingested
8599
+ knowledge.`
8600
+ },
8601
+ {
8602
+ name: "rate",
8603
+ raw: `---
8604
+ name: rate
8605
+ description: Give feedback on the last answer
8606
+ section: More
8607
+ args: good [note] | bad <note>
8608
+ handler: ../commands/rate.ts
8609
+ ---
8610
+
8611
+ Tell the analyst how its last answer landed. \`/rate good\` reinforces the
8612
+ approach; \`/rate bad <what was off>\` records a correction. Feedback becomes a
8613
+ durable preference so the analyst gets better at working with you over time.`
8614
+ },
8615
+ {
8616
+ name: "knowledge",
8617
+ raw: `---
8618
+ name: knowledge
8619
+ description: Ingest external case studies & frameworks
8620
+ section: More
8621
+ args: [add <file> | list]
8622
+ handler: ../commands/knowledge.ts
8623
+ ---
8624
+
8625
+ Teach the analyst from work done outside the platform. \`/knowledge add <file>\`
8626
+ ingests a markdown, text, or PDF case study, framework, or benchmark report and
8627
+ indexes it for retrieval during analysis. \`/knowledge list\` shows what's
8628
+ indexed. Drop files into ~/.ntrp/knowledge to stage them.`
8629
+ },
8630
+ {
8631
+ name: "ingest",
8632
+ raw: `---
8633
+ name: ingest
8634
+ description: Import CRM CSV exports (or --demo)
8635
+ section: Hidden
8636
+ hidden: true
8637
+ args: <file> | --demo [--scenario <name>]
8638
+ handler: ../commands/ingest.ts
8639
+ ---
8640
+
8641
+ Import a CSV file from your CRM (Salesforce, HubSpot, Outreach). The command
8642
+ auto-detects the entity type based on column headers and runs identity
8643
+ resolution after import.
8644
+
8645
+ Pass \`--demo\` instead of a file to generate a synthetic dataset shaped by
8646
+ your company profile. Accepts \`--scenario <name>\` to pick a scenario (else
8647
+ random) and \`--regen-taxonomy\` to rebuild the profile-derived market
8648
+ taxonomy. Rep names draw from a curated music / sports / film roster for a
8649
+ little demo delight; pass \`--no-whimsy\` to use generic names instead.`
8650
+ },
8651
+ {
8652
+ name: "demo",
8653
+ raw: `---
8654
+ name: demo
8655
+ description: Generate demo scenario data
8656
+ section: Getting Started
8657
+ args: [--scenario <name>] [--regen-taxonomy] [--no-whimsy]
8658
+ handler: ../commands/generate.ts
8659
+ hidden: true
8660
+ ---
8661
+
8662
+ Generate a complete dataset for one of 5 demo scenarios: hidden_crisis,
8663
+ leaky_bucket, stale_pipeline, lone_wolf, busy_bees. Without \`--scenario\`,
8664
+ picks one at random each run. Use \`--list-scenarios\` to see descriptions.
8665
+ Use \`--regen-taxonomy\` to force a fresh AI-built market taxonomy.
8666
+ By default, sales rep names are drawn from a curated music / sports / film
8667
+ roster; pass \`--no-whimsy\` for generic placeholder names.
8668
+
8669
+ This command is hidden \u2014 prefer \`/ingest --demo\` which delegates here.`
8670
+ },
8671
+ {
8672
+ name: "strategy",
8673
+ raw: `---
8674
+ name: strategy
8675
+ description: Ingest and manage GTM strategies
8676
+ section: More
8677
+ args: [list|ingest|add|show|sync|sources] [file|text|id|folder]
8678
+ handler: ../commands/strategy.ts
8679
+ ---
8680
+
8681
+ Add user-authored or agent-authored GTM strategies to NTRP's strategy library.
8682
+ \`/strategy ingest <file>\` accepts markdown, YAML, PDF, or plain text files.
8683
+ \`/strategy ingest -\` reads strategy text from stdin, and \`/strategy add "..."\`
8684
+ creates a strategy from a short pasted description.
8685
+ \`/strategy sync --path <folder>\` scans an Obsidian-style local library and
8686
+ imports supported strategy documents. Use \`/strategy sources\` to see available
8687
+ library connector types.`
8688
+ },
8689
+ {
8690
+ name: "segment",
8691
+ raw: `---
8692
+ name: segment
8693
+ description: Browse and inspect segments
8694
+ section: More
8695
+ args: [list|show|compare|create|delete] [args]
8696
+ handler: ../commands/segment.ts
8697
+ ---
8698
+
8699
+ Browse, inspect, and manage data segments. With no arguments, lists all
8700
+ segments sorted worst-first. Subcommands: \`show <name>\`, \`compare <a> <b>\`,
8701
+ \`create <name> --entity <type> --filter <expr>\`, \`delete <name>\`.`
8702
+ },
8703
+ {
8704
+ name: "report",
8705
+ raw: `---
8706
+ name: report
8707
+ description: Export latest diagnosis
8708
+ section: More
8709
+ args: [--format terminal|md|json] [--output <file>]
8710
+ handler: ../commands/report.ts
8711
+ ---
8712
+
8713
+ Export the most recent diagnosis as terminal output, markdown, or JSON. Use
8714
+ \`--output <file>\` to write to disk instead of stdout.`
8715
+ },
8716
+ {
8717
+ name: "progress",
8718
+ raw: `---
8719
+ name: progress
8720
+ description: Usage stats and milestone ladder
8721
+ section: Navigation
8722
+ args: [reset] [--confirm]
8723
+ handler: ../commands/progress.ts
8724
+ ---
8725
+
8726
+ Hours saved, weekly activity trend, session counts, AI token usage, and the
8727
+ full milestone ladder with progress bars. Use reset (type "reset" to confirm)
8728
+ to clear hours and milestones while keeping this install's identity.`
8729
+ },
8730
+ {
8731
+ name: "status",
8732
+ raw: `---
8733
+ name: status
8734
+ description: Show last diagnosis and entity counts
8735
+ section: More
8736
+ handler: ../commands/status.ts
8737
+ ---
8738
+
8739
+ Show what data you currently have loaded and the result of your last
8740
+ diagnosis, if any.`
8741
+ },
8742
+ {
8743
+ name: "scratch",
8744
+ raw: `---
8745
+ name: scratch
8746
+ description: Wipe config, profile, and all datasets
8747
+ section: Admin
8748
+ args: [--confirm] [--include-progress]
8749
+ handler: ../commands/scratch.ts
8750
+ hidden: true
8751
+ ---
8752
+
8753
+ Minimal factory reset: removes API key, config, company profile, all sessions,
8754
+ per-session datasets, and demo taxonomy cache. Preserves progress (hours saved)
8755
+ by default. Pass \`--include-progress\` to also wipe install identity and hours.
8756
+ Also preserves memory, strategies, wins, knowledge, exports, and audit. Requires
8757
+ typing \`scratch\` in the REPL or passing \`--confirm\` one-shot. Triggers
8758
+ onboarding on next interactive use.`
8759
+ },
8760
+ {
8761
+ name: "cleanup",
8762
+ raw: `---
8763
+ name: cleanup
8764
+ description: Close all active sessions
8765
+ section: Admin
8766
+ args: [--confirm]
8767
+ handler: ../commands/cleanup.ts
8768
+ hidden: true
8769
+ ---
8770
+
8771
+ Mark every in-progress session as ended without deleting transcripts or dataset
8772
+ files. Interactive REPL only. Confirm with y/N or \`--confirm\` one-shot.`
8773
+ },
8774
+ {
8775
+ name: "deactivate-demo",
8776
+ raw: `---
8777
+ name: deactivate-demo
8778
+ description: Disable demo data generators
8779
+ section: Admin
8780
+ args: [--confirm]
8781
+ handler: ../commands/deactivate-demo.ts
8782
+ hidden: true
8783
+ ---
8784
+
8785
+ Persistently disable demo generators (\`/ingest --demo\`, \`/new --demo\`, NL
8786
+ "use demo data"). Re-enable with \`/config set demo-enabled true\`.`
8787
+ },
8788
+ {
8789
+ name: "reset",
8790
+ raw: `---
8791
+ name: reset
8792
+ description: Clear all data and start fresh
8793
+ section: More
8794
+ args: [--force]
8795
+ handler: ../commands/reset.ts
8796
+ ---
8797
+
8798
+ Drop all rows from every table in the local DuckDB database. Requires
8799
+ \`--force\` to proceed.`
8800
+ },
8801
+ {
8802
+ name: "playbook",
8803
+ raw: `---
8804
+ name: playbook
8805
+ description: Show or extend recommended plays
8806
+ section: More
8807
+ args: [--vital-sign <name>] [play-id] | add
8808
+ handler: ../commands/playbook.ts
8809
+ ---
8810
+
8811
+ Show the playbook of recommended plays keyed to each vital sign. Pass a
8812
+ play-id to drill into a single play's steps and expected outcome. Run
8813
+ \`/playbook add\` and the analyst walks you through capturing a new play, step by
8814
+ step \u2014 no flags or quoting needed. Learned plays become recommendable during
8815
+ analysis. (Power users can still pass everything as flags in one shot.)`
8816
+ },
8817
+ {
8818
+ name: "export",
8819
+ raw: `---
8820
+ name: export
8821
+ description: Save diagnosis to Obsidian notes
8822
+ section: More
8823
+ args: [--dir <path>] [--segment <name>]
8824
+ handler: ../commands/export.ts
8825
+ ---
8826
+
8827
+ Write the most recent diagnosis to your configured notes directory as
8828
+ markdown, ready for Obsidian, Logseq, or any other note tool.`
8829
+ },
8830
+ {
8831
+ name: "publish",
8832
+ raw: `---
8833
+ name: publish
8834
+ description: Preview and propose repository exports
8835
+ section: More
8836
+ args: [preview|propose|targets] [--target markdown] [--dir <path>]
8837
+ handler: ../commands/publish.ts
8838
+ ---
8839
+
8840
+ Build a full repository export package from the latest diagnosis, findings,
8841
+ strategies, evidence, and action receipts. \`preview\` shows the write plan;
8842
+ \`propose\` creates an approval-gated action proposal. The first executable
8843
+ target is local markdown for Obsidian-compatible repositories. Notion,
8844
+ Airtable, and GitHub mappings are documented via \`/publish targets\`.`
8845
+ },
8846
+ {
8847
+ name: "backmeup",
8848
+ raw: `---
8849
+ name: backmeup
8850
+ description: Export diagnosis receipts as CSV
8851
+ section: More
8852
+ args: [--output <dir>]
8853
+ handler: ../commands/backmeup.ts
8854
+ ---
8855
+
8856
+ Export your latest diagnosis as a folder of CSV files you can attach to a
8857
+ Slack thread, email, or slide deck. Creates a timestamped folder under
8858
+ ~/.ntrp/exports/ containing a cover sheet with headline numbers, a findings
8859
+ file, and per-vital-sign evidence CSVs showing exactly which deals, contacts,
8860
+ or orgs drove each score. Use --output <dir> to write somewhere else.`
8861
+ },
8862
+ {
8863
+ name: "profile",
8864
+ raw: `---
8865
+ name: profile
8866
+ description: Set sales motion
8867
+ section: Settings
8868
+ args: [list|set|show] [preset]
8869
+ handler: ../commands/profile.ts
8870
+ ---
8871
+
8872
+ Choose a sales motion preset (PLG, SMB Velocity, Mid-Market, Enterprise). Each
8873
+ preset adjusts the vital-sign thresholds to match your deal cycle.`
8874
+ },
8875
+ {
8876
+ name: "connect",
8877
+ raw: `---
8878
+ name: connect
8879
+ description: Connect an AI provider (paste any key)
8880
+ section: Settings
8881
+ args: [provider] [--key <key>] [--base-url <url> --id <name>]
8882
+ handler: ../commands/connect.ts
8883
+ ---
8884
+
8885
+ Paste any provider's API key \u2014 NTRP identifies the provider from the key
8886
+ format (probing ambiguous ones), validates it, discovers which models the key
8887
+ can use, and builds the HIGH/MEDIUM/LOW tier stack automatically.
8888
+
8889
+ Works with Anthropic, OpenAI, Google Gemini, Groq, Mistral, DeepSeek, xAI,
8890
+ OpenRouter, Together, and Fireworks out of the box. \`/connect ollama\` wires a
8891
+ local Ollama; \`/connect --base-url <url> --id <name>\` registers any other
8892
+ OpenAI-compatible endpoint.`
8893
+ },
8894
+ {
8895
+ name: "config",
8896
+ raw: `---
8897
+ name: config
8898
+ description: Get/set config values
8899
+ section: Settings
8900
+ args: [get|set|list|delete] <key> [value]
8901
+ handler: ../commands/config.ts
8902
+ ---
8903
+
8904
+ Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
8905
+ \`api-key\` (Anthropic), \`openai-api-key\` (and \`groq-api-key\`, \`google-api-key\`, ...),
8906
+ \`llm-primary\` (default engine), \`llm-tier\`, \`llm-auto-failover\`,
8907
+ \`default-format\`, \`export-dir\`.
8908
+
8909
+ Setting a provider key opens a hidden prompt and auto-discovers that
8910
+ provider's models. Prefer \`/connect\` \u2014 it detects the provider for you.`
8911
+ },
8912
+ {
8913
+ name: "provider",
8914
+ raw: `---
8915
+ name: provider
8916
+ description: Switch active LLM engine
8917
+ section: Settings
8918
+ args: [<id>|list|reset|save|failover on|off]
8919
+ handler: ../commands/provider.ts
8920
+ ---
8921
+
8922
+ Choose which connected engine answers this session \u2014 any provider added via
8923
+ \`/connect\` (anthropic, openai, groq, google, ollama, custom endpoints, ...).
8924
+ Session-scoped by default; \`/provider save\` writes the default to config.
8925
+ \`/provider failover on\` enables rate-limit auto-failover.`
8926
+ },
8927
+ {
8928
+ name: "tier",
8929
+ raw: `---
8930
+ name: tier
8931
+ description: Set inference tier (HIGH/MEDIUM/LOW)
8932
+ section: Settings
8933
+ args: [high|medium|low|list] [--default]
8934
+ handler: ../commands/tier.ts
8935
+ ---
8936
+
8937
+ Set quality/cost tier for this REPL session. Agentic surfaces respect your tier;
8938
+ some single-shot surfaces keep fixed defaults. \`/tier list\` highlights the
8939
+ active stack. Add \`--default\` to persist to config.`
8940
+ },
8941
+ {
8942
+ name: "model",
8943
+ raw: `---
8944
+ name: model
8945
+ description: Override the active LLM model
8946
+ section: Settings
8947
+ args: [list|set <id>|refresh|clear] [--default]
8948
+ handler: ../commands/model.ts
8949
+ ---
8950
+
8951
+ \`/model list\` shows the models discovered for the active engine with their
8952
+ tier assignments. \`/model refresh\` re-discovers the live list. \`/model set <id>\`
8953
+ pins a model on the **active engine**; cross-provider IDs are rejected \u2014
8954
+ switch with \`/provider\` first.`
8955
+ },
8956
+ {
8957
+ name: "activate",
8958
+ raw: `---
8959
+ name: activate
8960
+ description: Enter license key
8961
+ section: Settings
8962
+ args: <license>
8963
+ handler: ../commands/activate.ts
8964
+ ---
8965
+
8966
+ Activate NTRP with your license key (format: NTRP-XXXX-XXXX-XXXX). Most
8967
+ commands require a valid license.`
8968
+ },
8969
+ {
8970
+ name: "upgrade",
8971
+ raw: `---
8972
+ name: upgrade
8973
+ description: Upgrade trial to Pro \u2014 checkout + paste key
8974
+ section: Settings
8975
+ handler: ../commands/upgrade.ts
8976
+ ---
8977
+
8978
+ Open the Pro checkout page and paste your new license key without leaving
8979
+ the REPL. Use during trial grace or after cutoff. Flags: --url (print checkout URL only).`
8980
+ },
8981
+ {
8982
+ name: "checkout",
8983
+ raw: `---
8984
+ name: checkout
8985
+ description: Open signup checkout in your browser
8986
+ section: Settings
8987
+ handler: ../commands/checkout.ts
8988
+ ---
8989
+
8990
+ Opens the Lemon Squeezy checkout page in your default browser. Use anytime
8991
+ you need a trial or Pro license key.`
8992
+ },
8993
+ {
8994
+ name: "feedback",
8995
+ raw: `---
8996
+ name: feedback
8997
+ description: Correct your profile in plain English
8998
+ section: Settings
8999
+ args: <correction>
9000
+ handler: ../commands/feedback.ts
9001
+ ---
9002
+
9003
+ Apply natural-language corrections to your company profile. Maps structured
9004
+ fields when possible (e.g. "our sales cycle is 6 months" updates
9005
+ sales_cycle_days) and merges remaining nuances into a custom_context
9006
+ paragraph that flows into all AI surfaces.`
9007
+ }
9008
+ ];
9009
+ }
9010
+ });
9011
+
8282
9012
  // src/ai/prompt-parts.ts
8283
9013
  function buildCompanyProfileBlock() {
8284
9014
  const p = loadProfile();
@@ -8305,12 +9035,41 @@ function buildPlaybookBlock() {
8305
9035
  Learned plays (added from this team's experience and ingested case studies \u2014 recommend these when they fit):
8306
9036
  ${learned}`;
8307
9037
  }
8308
- var ANALYST_INSTINCT_BLOCK, VITAL_SIGNS_BLOCK, PLAYBOOK_BLOCK, METRICS_BLOCK, FINDINGS_SCHEMA_BLOCK;
9038
+ function buildCommandCatalogBlock() {
9039
+ const GROUP_ANALYSIS = "Analysis & data";
9040
+ const GROUP_SESSION = "Session, memory & outputs";
9041
+ const GROUP_SETTINGS = "Settings & providers";
9042
+ const groupOrder = [GROUP_ANALYSIS, GROUP_SESSION, GROUP_SETTINGS];
9043
+ const groupFor = (section) => {
9044
+ if (section === "Hidden" || section === "Getting Started") return GROUP_ANALYSIS;
9045
+ if (section === "Settings") return GROUP_SETTINGS;
9046
+ return GROUP_SESSION;
9047
+ };
9048
+ const groups = new Map(groupOrder.map((label) => [label, []]));
9049
+ for (const meta of listWorkflows(true)) {
9050
+ if (meta.name === "ask") continue;
9051
+ if (meta.section === "Admin") continue;
9052
+ groups.get(groupFor(meta.section)).push(formatCatalogLine(meta));
9053
+ }
9054
+ groups.get(GROUP_SESSION).push(
9055
+ "- /help \u2014 Show the shortcut list",
9056
+ "- /home \u2014 Show the welcome dashboard and current session status"
9057
+ );
9058
+ return groupOrder.filter((label) => groups.get(label).length > 0).map((label) => `${label}:
9059
+ ${groups.get(label).join("\n")}`).join("\n\n");
9060
+ }
9061
+ function formatCatalogLine(meta) {
9062
+ const args = meta.args?.trim() ? ` ${meta.args.trim()}` : "";
9063
+ const note = DESTRUCTIVE_COMMAND_NOTES[meta.name] ? ` (${DESTRUCTIVE_COMMAND_NOTES[meta.name]})` : "";
9064
+ return `- /${meta.name}${args} \u2014 ${meta.description}${note}`;
9065
+ }
9066
+ var ANALYST_INSTINCT_BLOCK, VITAL_SIGNS_BLOCK, PLAYBOOK_BLOCK, DESTRUCTIVE_COMMAND_NOTES, METRICS_BLOCK, FINDINGS_SCHEMA_BLOCK;
8309
9067
  var init_prompt_parts = __esm({
8310
9068
  "src/ai/prompt-parts.ts"() {
8311
9069
  "use strict";
8312
9070
  init_profile();
8313
9071
  init_playbook();
9072
+ init_registry();
8314
9073
  ANALYST_INSTINCT_BLOCK = `A search box returns numbers; a world-class analyst returns insight. The instincts below are what separate the two. Bring them PROACTIVELY \u2014 surface the connection, the chain, and the priority without waiting to be asked, because the user often doesn't know to ask.
8315
9074
 
8316
9075
  - ROOT CAUSE OVER SYMPTOM LIST. GTM problems are rarely independent \u2014 they're usually one failure wearing several masks. When two or more vital signs are red or yellow, your first instinct is to ask "is this the same underlying problem showing up in different places?" and, when it is, name the single cause. (Classic shape: a broken lead handoff starves reps of new pipeline \u2192 they work only what they can already see \u2192 everything else ages into stale, zombie deals \u2192 the forecast inflates with deals nobody is touching \u2192 the quarter is quietly at risk. One cause, four symptoms.)
@@ -8333,6 +9092,9 @@ Restraint matters: NTRP is a stethoscope, not a surgeon. Observe, connect, and r
8333
9092
  - "Fix the Handoff Gap" (id: fix-handoff-gap) \u2014 when drop_rate is high
8334
9093
  - "Retarget Misdirected Effort" (id: retarget-effort) \u2014 when signal_to_noise is low
8335
9094
  - "Unstick the Pipeline" (id: unstick-pipeline) \u2014 when flow_rate is low`;
9095
+ DESTRUCTIVE_COMMAND_NOTES = {
9096
+ reset: "destructive \u2014 wipes all data, requires --force"
9097
+ };
8336
9098
  METRICS_BLOCK = `Revenue metrics measure GTM output \u2014 the standard SaaS metrics:
8337
9099
  - ARR: Total closed-won revenue. New ARR + Expansion ARR = growth; Churned + Contraction = leakage.
8338
9100
  - NRR (Net Revenue Retention): >100% means growing from existing customers. Best-in-class: 110%+.
@@ -13889,15 +14651,18 @@ function inferHandoffTarget(input) {
13889
14651
  return "plan";
13890
14652
  }
13891
14653
  function isShipIntent(input) {
13892
- return /\b(handoff|ship|deliver|board memo|export|turn this into|write.?up|action plan)\b/i.test(
13893
- input.trim()
13894
- );
14654
+ const line = input.trim();
14655
+ if (/\?\s*$/.test(line) || QUESTION_LEAD_RE.test(line)) return false;
14656
+ return SHIP_INTENT_RE.test(line);
13895
14657
  }
14658
+ var QUESTION_LEAD_RE, SHIP_INTENT_RE;
13896
14659
  var init_handoff_draft = __esm({
13897
14660
  "src/conversation/handoff-draft.ts"() {
13898
14661
  "use strict";
13899
14662
  init_profile();
13900
14663
  init_session_analysis();
14664
+ QUESTION_LEAD_RE = /^\s*(what|why|how|when|where|who|which|is|are|was|were|do|does|did|explain|tell me|help me understand)\b/i;
14665
+ SHIP_INTENT_RE = /\b(ship|export|deliver|write[- ]?up|board memo|action plan|turn (this|it|that) into|(draft|create|make|build|prepare|generate|send)\s+(me\s+)?(a\s+|the\s+)?hand[- ]?off|hand[- ]?off\s+(prompt|doc|document|plan))\b/i;
13901
14666
  }
13902
14667
  });
13903
14668
 
@@ -14459,6 +15224,16 @@ FORMATTING (brief mode \u2014 scannable, not chunky):
14459
15224
  - No ### headings, no tables, no --- footer, no closing "want me to dig deeper?" question.
14460
15225
  - Single-point answers can stay one sentence \u2014 don't force bullets when one line is enough.
14461
15226
  `;
15227
+ const commandStyleRule = responseMode === "brief" ? '- Style in brief mode: a suggestion is at most ONE short trailing line after your answer (e.g. "`/segment compare enterprise smb` gives the full side-by-side.") \u2014 no heading, no footer.' : "- Style: weave the suggestion naturally into your close \u2014 one sentence with the exact invocation in `inline code`.";
15228
+ const commandSection = `PRESET COMMANDS (slash commands the user can type at the prompt \u2014 you may SUGGEST these; you have no ability to run them):
15229
+ ${buildCommandCatalogBlock()}
15230
+
15231
+ COMMAND SUGGESTION RULES (suggest-only):
15232
+ - Evaluate whether the user's question overlaps a preset command's capability. Answer the question yourself first, from tools and context; suggest a command only when it adds capability beyond your answer \u2014 writing files, opening a wizard, running a heavier analysis, or changing settings.
15233
+ - If the message is primarily a request to DO what a command does (e.g. "export this to my notes"), say plainly that you don't execute commands and give the exact invocation to type, e.g. \`/handoff notes\`.
15234
+ - Never claim a command was run. Never invent flags \u2014 use only the documented arguments above. At most ONE command suggestion per answer; most answers need none.
15235
+ - Only suggest a command that can work right now given the session state you know (e.g. no \`/diagnose\` or \`/export\` before data or a diagnosis exists).
15236
+ ${commandStyleRule}`;
14462
15237
  const outputRules = responseMode === "brief" ? `OUTPUT RULES:
14463
15238
  - Respond in plain text markdown (not JSON). Lead sentence + bullets when helpful \u2014 never a dense paragraph wall.
14464
15239
  - Pull numbers from the completed session analysis or conversation history \u2014 never guess.
@@ -14482,6 +15257,8 @@ ${VITAL_SIGNS_BLOCK}
14482
15257
 
14483
15258
  PLAYBOOK \u2014 recommend these plays when appropriate:
14484
15259
  ${buildPlaybookBlock()}
15260
+
15261
+ ${commandSection}
14485
15262
  ${formattingSection}
14486
15263
  ${outputRules}`;
14487
15264
  }
@@ -14742,6 +15519,136 @@ var init_embeddings = __esm({
14742
15519
  }
14743
15520
  });
14744
15521
 
15522
+ // src/memory/retrieval.ts
15523
+ function tokenize(text) {
15524
+ return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3 && !STOPWORDS.has(t));
15525
+ }
15526
+ function keywordRank(query, items, topK) {
15527
+ const queryTokens = new Set(tokenize(query));
15528
+ if (queryTokens.size === 0) {
15529
+ return items.slice(-topK).reverse().map((it) => ({ id: it.id, score: 0 }));
15530
+ }
15531
+ const scored = items.map((it) => {
15532
+ const tokens = tokenize(it.text);
15533
+ if (tokens.length === 0) return { id: it.id, score: 0 };
15534
+ let overlap = 0;
15535
+ const seen = /* @__PURE__ */ new Set();
15536
+ for (const t of tokens) {
15537
+ if (queryTokens.has(t) && !seen.has(t)) {
15538
+ overlap++;
15539
+ seen.add(t);
15540
+ }
15541
+ }
15542
+ return { id: it.id, score: overlap / Math.sqrt(tokens.length) };
15543
+ });
15544
+ const positive = scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score);
15545
+ if (positive.length > 0) return positive.slice(0, topK);
15546
+ return items.slice(-topK).reverse().map((it) => ({ id: it.id, score: 0 }));
15547
+ }
15548
+ function cosine(a, b) {
15549
+ let dot = 0;
15550
+ let na = 0;
15551
+ let nb = 0;
15552
+ const len = Math.min(a.length, b.length);
15553
+ for (let i = 0; i < len; i++) {
15554
+ dot += a[i] * b[i];
15555
+ na += a[i] * a[i];
15556
+ nb += b[i] * b[i];
15557
+ }
15558
+ if (na === 0 || nb === 0) return 0;
15559
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
15560
+ }
15561
+ async function rankByRelevance(query, items, topK) {
15562
+ if (items.length === 0) return [];
15563
+ try {
15564
+ const { isEmbeddingsEnabled: isEmbeddingsEnabled2, embedText: embedText2, embedItems: embedItems2 } = await Promise.resolve().then(() => (init_embeddings(), embeddings_exports));
15565
+ if (isEmbeddingsEnabled2()) {
15566
+ const queryVec = await embedText2(query);
15567
+ if (queryVec) {
15568
+ const withVecs = await embedItems2(items);
15569
+ const scored = withVecs.filter((it) => it.embedding && it.embedding.length > 0).map((it) => ({ id: it.id, score: cosine(queryVec, it.embedding) })).sort((a, b) => b.score - a.score);
15570
+ if (scored.length > 0) return scored.slice(0, topK);
15571
+ }
15572
+ }
15573
+ } catch {
15574
+ }
15575
+ return keywordRank(query, items, topK);
15576
+ }
15577
+ var STOPWORDS;
15578
+ var init_retrieval = __esm({
15579
+ "src/memory/retrieval.ts"() {
15580
+ "use strict";
15581
+ STOPWORDS = /* @__PURE__ */ new Set([
15582
+ "the",
15583
+ "and",
15584
+ "for",
15585
+ "are",
15586
+ "was",
15587
+ "were",
15588
+ "with",
15589
+ "that",
15590
+ "this",
15591
+ "from",
15592
+ "have",
15593
+ "has",
15594
+ "had",
15595
+ "our",
15596
+ "your",
15597
+ "you",
15598
+ "what",
15599
+ "which",
15600
+ "how",
15601
+ "why",
15602
+ "who",
15603
+ "when",
15604
+ "where",
15605
+ "into",
15606
+ "about",
15607
+ "over",
15608
+ "than",
15609
+ "then",
15610
+ "they",
15611
+ "them",
15612
+ "their",
15613
+ "there",
15614
+ "here",
15615
+ "been",
15616
+ "being",
15617
+ "does",
15618
+ "did",
15619
+ "doing",
15620
+ "can",
15621
+ "could",
15622
+ "would",
15623
+ "should",
15624
+ "will",
15625
+ "shall",
15626
+ "may",
15627
+ "might",
15628
+ "must",
15629
+ "not",
15630
+ "but",
15631
+ "all",
15632
+ "any",
15633
+ "some",
15634
+ "more",
15635
+ "most",
15636
+ "much",
15637
+ "many",
15638
+ "very",
15639
+ "just",
15640
+ "like",
15641
+ "out",
15642
+ "off",
15643
+ "per",
15644
+ "via",
15645
+ "use",
15646
+ "get",
15647
+ "got"
15648
+ ]);
15649
+ }
15650
+ });
15651
+
14745
15652
  // src/strategies/readers.ts
14746
15653
  import { createHash } from "crypto";
14747
15654
  import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
@@ -14822,6 +15729,37 @@ var init_readers = __esm({
14822
15729
  }
14823
15730
  });
14824
15731
 
15732
+ // src/memory/knowledge.ts
15733
+ import { existsSync as existsSync16, readFileSync as readFileSync14, appendFileSync as appendFileSync3, readdirSync as readdirSync2 } from "fs";
15734
+ import { join as join13 } from "path";
15735
+ import { randomUUID as randomUUID4 } from "crypto";
15736
+ function knowledgePath() {
15737
+ return join13(getMemoryDir(), KNOWLEDGE_FILE);
15738
+ }
15739
+ function loadKnowledgeChunks() {
15740
+ const path = knowledgePath();
15741
+ if (!existsSync16(path)) return [];
15742
+ const out = [];
15743
+ for (const line of readFileSync14(path, "utf-8").split("\n")) {
15744
+ const trimmed = line.trim();
15745
+ if (!trimmed) continue;
15746
+ try {
15747
+ out.push(JSON.parse(trimmed));
15748
+ } catch {
15749
+ }
15750
+ }
15751
+ return out;
15752
+ }
15753
+ var KNOWLEDGE_FILE;
15754
+ var init_knowledge = __esm({
15755
+ "src/memory/knowledge.ts"() {
15756
+ "use strict";
15757
+ init_store();
15758
+ init_readers();
15759
+ KNOWLEDGE_FILE = "knowledge.jsonl";
15760
+ }
15761
+ });
15762
+
14825
15763
  // src/ai/strategy-normalize.ts
14826
15764
  function stripFences2(text) {
14827
15765
  const trimmed = text.trim();
@@ -15352,175 +16290,10 @@ var init_strategy = __esm({
15352
16290
  }
15353
16291
  });
15354
16292
 
15355
- // src/investigation/verbosity-cli.ts
15356
- init_context2();
15357
- init_schema();
15358
-
15359
- // src/services/verbosity-investigation.ts
15360
- init_context2();
15361
- init_schema();
15362
- init_agentic_loop();
15363
- init_health_score();
15364
- init_divergence();
15365
-
15366
16293
  // src/memory/store.ts
15367
- init_store();
15368
16294
  import { existsSync as existsSync17, readFileSync as readFileSync15, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync11 } from "fs";
15369
16295
  import { join as join16 } from "path";
15370
16296
  import { randomUUID as randomUUID5 } from "crypto";
15371
-
15372
- // src/memory/retrieval.ts
15373
- var STOPWORDS = /* @__PURE__ */ new Set([
15374
- "the",
15375
- "and",
15376
- "for",
15377
- "are",
15378
- "was",
15379
- "were",
15380
- "with",
15381
- "that",
15382
- "this",
15383
- "from",
15384
- "have",
15385
- "has",
15386
- "had",
15387
- "our",
15388
- "your",
15389
- "you",
15390
- "what",
15391
- "which",
15392
- "how",
15393
- "why",
15394
- "who",
15395
- "when",
15396
- "where",
15397
- "into",
15398
- "about",
15399
- "over",
15400
- "than",
15401
- "then",
15402
- "they",
15403
- "them",
15404
- "their",
15405
- "there",
15406
- "here",
15407
- "been",
15408
- "being",
15409
- "does",
15410
- "did",
15411
- "doing",
15412
- "can",
15413
- "could",
15414
- "would",
15415
- "should",
15416
- "will",
15417
- "shall",
15418
- "may",
15419
- "might",
15420
- "must",
15421
- "not",
15422
- "but",
15423
- "all",
15424
- "any",
15425
- "some",
15426
- "more",
15427
- "most",
15428
- "much",
15429
- "many",
15430
- "very",
15431
- "just",
15432
- "like",
15433
- "out",
15434
- "off",
15435
- "per",
15436
- "via",
15437
- "use",
15438
- "get",
15439
- "got"
15440
- ]);
15441
- function tokenize(text) {
15442
- return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3 && !STOPWORDS.has(t));
15443
- }
15444
- function keywordRank(query, items, topK) {
15445
- const queryTokens = new Set(tokenize(query));
15446
- if (queryTokens.size === 0) {
15447
- return items.slice(-topK).reverse().map((it) => ({ id: it.id, score: 0 }));
15448
- }
15449
- const scored = items.map((it) => {
15450
- const tokens = tokenize(it.text);
15451
- if (tokens.length === 0) return { id: it.id, score: 0 };
15452
- let overlap = 0;
15453
- const seen = /* @__PURE__ */ new Set();
15454
- for (const t of tokens) {
15455
- if (queryTokens.has(t) && !seen.has(t)) {
15456
- overlap++;
15457
- seen.add(t);
15458
- }
15459
- }
15460
- return { id: it.id, score: overlap / Math.sqrt(tokens.length) };
15461
- });
15462
- const positive = scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score);
15463
- if (positive.length > 0) return positive.slice(0, topK);
15464
- return items.slice(-topK).reverse().map((it) => ({ id: it.id, score: 0 }));
15465
- }
15466
- function cosine(a, b) {
15467
- let dot = 0;
15468
- let na = 0;
15469
- let nb = 0;
15470
- const len = Math.min(a.length, b.length);
15471
- for (let i = 0; i < len; i++) {
15472
- dot += a[i] * b[i];
15473
- na += a[i] * a[i];
15474
- nb += b[i] * b[i];
15475
- }
15476
- if (na === 0 || nb === 0) return 0;
15477
- return dot / (Math.sqrt(na) * Math.sqrt(nb));
15478
- }
15479
- async function rankByRelevance(query, items, topK) {
15480
- if (items.length === 0) return [];
15481
- try {
15482
- const { isEmbeddingsEnabled: isEmbeddingsEnabled2, embedText: embedText2, embedItems: embedItems2 } = await Promise.resolve().then(() => (init_embeddings(), embeddings_exports));
15483
- if (isEmbeddingsEnabled2()) {
15484
- const queryVec = await embedText2(query);
15485
- if (queryVec) {
15486
- const withVecs = await embedItems2(items);
15487
- const scored = withVecs.filter((it) => it.embedding && it.embedding.length > 0).map((it) => ({ id: it.id, score: cosine(queryVec, it.embedding) })).sort((a, b) => b.score - a.score);
15488
- if (scored.length > 0) return scored.slice(0, topK);
15489
- }
15490
- }
15491
- } catch {
15492
- }
15493
- return keywordRank(query, items, topK);
15494
- }
15495
-
15496
- // src/memory/knowledge.ts
15497
- init_store();
15498
- init_readers();
15499
- import { existsSync as existsSync16, readFileSync as readFileSync14, appendFileSync as appendFileSync3, readdirSync as readdirSync2 } from "fs";
15500
- import { join as join13 } from "path";
15501
- import { randomUUID as randomUUID4 } from "crypto";
15502
- var KNOWLEDGE_FILE = "knowledge.jsonl";
15503
- function knowledgePath() {
15504
- return join13(getMemoryDir(), KNOWLEDGE_FILE);
15505
- }
15506
- function loadKnowledgeChunks() {
15507
- const path = knowledgePath();
15508
- if (!existsSync16(path)) return [];
15509
- const out = [];
15510
- for (const line of readFileSync14(path, "utf-8").split("\n")) {
15511
- const trimmed = line.trim();
15512
- if (!trimmed) continue;
15513
- try {
15514
- out.push(JSON.parse(trimmed));
15515
- } catch {
15516
- }
15517
- }
15518
- return out;
15519
- }
15520
-
15521
- // src/memory/store.ts
15522
- var FACTS_FILE = "facts.jsonl";
15523
- var LEDGER_FILE = "ledger.jsonl";
15524
16297
  function memPath(file) {
15525
16298
  return join16(getMemoryDir(), file);
15526
16299
  }
@@ -15637,14 +16410,19 @@ async function buildMemoryBlock(query, opts = {}) {
15637
16410
  }
15638
16411
  return sections.join("\n\n");
15639
16412
  }
15640
-
15641
- // src/services/verbosity-investigation.ts
15642
- init_context2();
15643
- init_phase();
16413
+ var FACTS_FILE, LEDGER_FILE;
16414
+ var init_store2 = __esm({
16415
+ "src/memory/store.ts"() {
16416
+ "use strict";
16417
+ init_store();
16418
+ init_retrieval();
16419
+ init_knowledge();
16420
+ FACTS_FILE = "facts.jsonl";
16421
+ LEDGER_FILE = "ledger.jsonl";
16422
+ }
16423
+ });
15644
16424
 
15645
16425
  // src/ai/thread.ts
15646
- var DEFAULT_THREAD_CHAR_BUDGET = 24e3;
15647
- var MIN_RECENT_MESSAGES = 6;
15648
16426
  function summarizeAssistantTurn(text, max = 360) {
15649
16427
  const plain = text.replace(/#{1,6}\s+/g, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/^---\s*$/gm, "").replace(/\s+/g, " ").trim();
15650
16428
  if (plain.length <= max) return plain;
@@ -15658,7 +16436,6 @@ function summarizeAssistantTurn(text, max = 360) {
15658
16436
  const trimmed = out.trim();
15659
16437
  return trimmed.length > 0 ? trimmed : plain.slice(0, max).replace(/\s+\S*$/, "") + "\u2026";
15660
16438
  }
15661
- var ASSISTANT_SUMMARIZE_THRESHOLD = 420;
15662
16439
  function mergeConsecutive(messages) {
15663
16440
  const out = [];
15664
16441
  for (const message of messages) {
@@ -15712,6 +16489,58 @@ function boundConversation(messages, charBudget = DEFAULT_THREAD_CHAR_BUDGET) {
15712
16489
  function distillThread(rawMessages, charBudget = DEFAULT_THREAD_CHAR_BUDGET) {
15713
16490
  return boundConversation(compactConversation(rawMessages), charBudget);
15714
16491
  }
16492
+ var DEFAULT_THREAD_CHAR_BUDGET, MIN_RECENT_MESSAGES, ASSISTANT_SUMMARIZE_THRESHOLD;
16493
+ var init_thread = __esm({
16494
+ "src/ai/thread.ts"() {
16495
+ "use strict";
16496
+ DEFAULT_THREAD_CHAR_BUDGET = 24e3;
16497
+ MIN_RECENT_MESSAGES = 6;
16498
+ ASSISTANT_SUMMARIZE_THRESHOLD = 420;
16499
+ }
16500
+ });
16501
+
16502
+ // src/ai/explore-mode.ts
16503
+ function isDeepDiveQuestion(question) {
16504
+ return DEEP_DIVE_PATTERNS.some((p) => p.test(question.trim()));
16505
+ }
16506
+ function resolveExploreResponseMode(question, ctx, priorTurnCount) {
16507
+ if (isDeepDiveQuestion(question)) return "deep";
16508
+ if (isAnalysisReady(ctx)) return "brief";
16509
+ if (ctx.stage === "analyzed" || ctx.analysis.completed.length > 0) return "brief";
16510
+ if (priorTurnCount === 0) return "deep";
16511
+ return "brief";
16512
+ }
16513
+ function parsePromptExperiment(raw) {
16514
+ if (!raw || raw === "production") return "production";
16515
+ if (raw === "baseline" || raw === "a" || raw === "b" || raw === "c") return raw;
16516
+ return "production";
16517
+ }
16518
+ var DEEP_DIVE_PATTERNS;
16519
+ var init_explore_mode = __esm({
16520
+ "src/ai/explore-mode.ts"() {
16521
+ "use strict";
16522
+ init_context2();
16523
+ DEEP_DIVE_PATTERNS = [
16524
+ /\b(break down|breakdown|drill|dig deeper|show me|list all|by rep|by stage|by segment|query|sql|detail|expand|elaborate|full analysis|walk me through|pull up|give me the data)\b/i,
16525
+ /\b(how many|which deals|which accounts|who owns|top \d+|every deal|all stuck)\b/i
16526
+ ];
16527
+ }
16528
+ });
16529
+
16530
+ // src/investigation/verbosity-cli.ts
16531
+ init_context2();
16532
+ init_schema();
16533
+
16534
+ // src/services/verbosity-investigation.ts
16535
+ init_context2();
16536
+ init_schema();
16537
+ init_agentic_loop();
16538
+ init_health_score();
16539
+ init_divergence();
16540
+ init_store2();
16541
+ init_context2();
16542
+ init_phase();
16543
+ init_thread();
15715
16544
 
15716
16545
  // src/ai/verbosity-metrics.ts
15717
16546
  function analyzeAnswerText(text) {
@@ -15744,29 +16573,8 @@ function formatInvestigationTable(rows) {
15744
16573
  return [header, ...lines].join("\n");
15745
16574
  }
15746
16575
 
15747
- // src/ai/explore-mode.ts
15748
- init_context2();
15749
- var DEEP_DIVE_PATTERNS = [
15750
- /\b(break down|breakdown|drill|dig deeper|show me|list all|by rep|by stage|by segment|query|sql|detail|expand|elaborate|full analysis|walk me through|pull up|give me the data)\b/i,
15751
- /\b(how many|which deals|which accounts|who owns|top \d+|every deal|all stuck)\b/i
15752
- ];
15753
- function isDeepDiveQuestion(question) {
15754
- return DEEP_DIVE_PATTERNS.some((p) => p.test(question.trim()));
15755
- }
15756
- function resolveExploreResponseMode(question, ctx, priorTurnCount) {
15757
- if (isDeepDiveQuestion(question)) return "deep";
15758
- if (isAnalysisReady(ctx)) return "brief";
15759
- if (ctx.stage === "analyzed" || ctx.analysis.completed.length > 0) return "brief";
15760
- if (priorTurnCount === 0) return "deep";
15761
- return "brief";
15762
- }
15763
- function parsePromptExperiment(raw) {
15764
- if (!raw || raw === "production") return "production";
15765
- if (raw === "baseline" || raw === "a" || raw === "b" || raw === "c") return raw;
15766
- return "production";
15767
- }
15768
-
15769
16576
  // src/services/verbosity-investigation.ts
16577
+ init_explore_mode();
15770
16578
  init_session_analysis();
15771
16579
  init_agentic_loop();
15772
16580
  var FOLLOW_UP_QUESTIONS = [
@@ -15895,6 +16703,7 @@ ${turn.answer}`;
15895
16703
  }
15896
16704
 
15897
16705
  // src/investigation/verbosity-cli.ts
16706
+ init_explore_mode();
15898
16707
  function runUnitChecks() {
15899
16708
  const verbose = analyzeAnswerText(`### Risk
15900
16709