@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.
@@ -5855,6 +5855,736 @@ var init_playbook = __esm({
5855
5855
  }
5856
5856
  });
5857
5857
 
5858
+ // src/workflows/registry.ts
5859
+ function parseFrontmatter(raw) {
5860
+ if (!raw.startsWith("---")) return { meta: {}, body: raw };
5861
+ const end = raw.indexOf("\n---", 3);
5862
+ if (end === -1) return { meta: {}, body: raw };
5863
+ const fm = raw.slice(3, end).trim();
5864
+ const body = raw.slice(end + 4).replace(/^\r?\n/, "");
5865
+ const meta = {};
5866
+ for (const line of fm.split("\n")) {
5867
+ const match = line.match(/^(\w+):\s*(.*)$/);
5868
+ if (!match) continue;
5869
+ let value = match[2].trim();
5870
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
5871
+ value = value.slice(1, -1);
5872
+ }
5873
+ meta[match[1]] = value;
5874
+ }
5875
+ return { meta, body: body.trim() };
5876
+ }
5877
+ function loadRegistry() {
5878
+ if (registry) return registry;
5879
+ registry = /* @__PURE__ */ new Map();
5880
+ for (const entry of EMBEDDED_WORKFLOWS) {
5881
+ const { meta: fm, body } = parseFrontmatter(entry.raw);
5882
+ const meta = {
5883
+ name: fm.name ?? entry.name,
5884
+ description: fm.description ?? "",
5885
+ section: fm.section ?? "Other",
5886
+ args: fm.args,
5887
+ handler: fm.handler ?? "",
5888
+ body,
5889
+ hidden: fm.hidden === "true"
5890
+ };
5891
+ registry.set(meta.name, { meta, handler: null });
5892
+ }
5893
+ return registry;
5894
+ }
5895
+ function listWorkflows(includeHidden = false) {
5896
+ const all2 = Array.from(loadRegistry().values()).map((e) => e.meta);
5897
+ return includeHidden ? all2 : all2.filter((m) => !m.hidden);
5898
+ }
5899
+ var registry, EMBEDDED_WORKFLOWS;
5900
+ var init_registry = __esm({
5901
+ "src/workflows/registry.ts"() {
5902
+ "use strict";
5903
+ registry = null;
5904
+ EMBEDDED_WORKFLOWS = [
5905
+ {
5906
+ name: "new",
5907
+ raw: `---
5908
+ name: new
5909
+ description: Start a new analysis \u2014 one menu picks data + first report
5910
+ section: Hidden
5911
+ hidden: true
5912
+ args: [<file.csv>] | --demo [--scenario <name>] | --empty [--lens health|metrics]
5913
+ handler: ../commands/new.ts
5914
+ ---
5915
+
5916
+ Start a fresh point-in-time analysis. Interactive mode uses **one menu**: demo \u2192
5917
+ health, demo \u2192 metrics, your CSV, or empty. Loads data and runs the first report
5918
+ (formulas only \u2014 no AI unless you add \`--findings\` later). Demo metrics works
5919
+ without \`/onboard\`; your own CSV needs a profile for metrics calibration.
5920
+ After the report, **ask questions in plain English** \u2014 no slash needed.`
5921
+ },
5922
+ {
5923
+ name: "end",
5924
+ raw: `---
5925
+ name: end
5926
+ description: Close the current analysis without a handoff
5927
+ section: Start
5928
+ args:
5929
+ handler: ../commands/end.ts
5930
+ ---
5931
+
5932
+ Mark the current session as finished even when you didn't produce a report or
5933
+ other output. It drops off the "in progress" list, saves your transcript and
5934
+ dataset anchor for later, and rotates you to a fresh empty session. Use
5935
+ \`/handoff\` instead when you want to ship something.`
5936
+ },
5937
+ {
5938
+ name: "session",
5939
+ raw: `---
5940
+ name: session
5941
+ description: Pick up or browse your analyses
5942
+ section: Hidden
5943
+ hidden: true
5944
+ args: [<id|name>] | new
5945
+ handler: ../commands/session.ts
5946
+ ---
5947
+
5948
+ Move between your point-in-time analyses. With no arguments, lists your
5949
+ sessions with unfinished work (reached insight, never delivered) surfaced
5950
+ first. Pass a session id (or type the 4-char suffix after listing) to pick it
5951
+ back up \u2014 this rebinds its dataset and conversation so you continue exactly
5952
+ where you left off. \`new\` starts a fresh analysis.`
5953
+ },
5954
+ {
5955
+ name: "handoff",
5956
+ raw: `---
5957
+ name: handoff
5958
+ description: Turn the analysis into an output
5959
+ section: Start
5960
+ args: [report|notes|csv|publish|prompt] [deck|asana|clay|plan]
5961
+ handler: ../commands/handoff.ts
5962
+ ---
5963
+
5964
+ Close the loop to action. Produce a markdown report, a notes export, CSV
5965
+ receipts, or a repository package \u2014 or generate a ready-to-paste prompt for
5966
+ another agent to build a review deck, an Asana project, a Clay table, or an
5967
+ action plan from this diagnosis. Producing an output marks the session
5968
+ delivered so it stops showing up as unfinished work.`
5969
+ },
5970
+ {
5971
+ name: "onboard",
5972
+ raw: `---
5973
+ name: onboard
5974
+ description: Set up your company profile
5975
+ section: Settings
5976
+ handler: ../commands/onboard.ts
5977
+ ---
5978
+
5979
+ Run the first-run wizard to build a rich company profile. Configures one or
5980
+ two LLM engines (Anthropic and/or OpenAI), then asks
5981
+ a few seed questions and uses AI to draft industry, ICP, deal size, and stack
5982
+ guesses. Profile is stored at \`~/.ntrp/profile.json\` and flows into every
5983
+ AI surface (findings, NL answers, demo generation).`
5984
+ },
5985
+ {
5986
+ name: "sessions",
5987
+ raw: `---
5988
+ name: sessions
5989
+ description: Browse past session history
5990
+ section: More
5991
+ args: [list|show <id>]
5992
+ handler: ../commands/sessions.ts
5993
+ hidden: true
5994
+ ---
5995
+
5996
+ List and inspect past REPL sessions. Shows session dates, AI-generated
5997
+ summaries, and exchange counts. Use \`show <id>\` to view the full
5998
+ conversation from a specific session.`
5999
+ },
6000
+ {
6001
+ name: "setup",
6002
+ raw: `---
6003
+ name: setup
6004
+ description: Configure NTRP for headless and agent use
6005
+ section: Settings
6006
+ args: check | agent [--profile <file|->]
6007
+ handler: ../commands/setup.ts
6008
+ ---
6009
+
6010
+ Validate local readiness or configure NTRP non-interactively for automation.
6011
+ \`setup check --json\` reports license, profile, API key, database, and writable
6012
+ directory state. \`setup agent\` accepts a profile JSON file or direct flags \u2014
6013
+ \`--llm-key <key>\` auto-detects the provider from any pasted key
6014
+ (\`--llm-provider <id>\` to force one).`
6015
+ },
6016
+ {
6017
+ name: "update",
6018
+ raw: `---
6019
+ name: update
6020
+ description: Update NTRP to the latest version
6021
+ section: Settings
6022
+ handler: ../commands/update.ts
6023
+ ---
6024
+
6025
+ Update the globally installed NTRP package via npm.`
6026
+ },
6027
+ {
6028
+ name: "resume",
6029
+ raw: `---
6030
+ name: resume
6031
+ description: Continue a previous session
6032
+ section: More
6033
+ args: [id]
6034
+ handler: ../commands/resume.ts
6035
+ hidden: true
6036
+ ---
6037
+
6038
+ Load a previous session's context so the AI can reference what was
6039
+ discussed before. Without an ID, resumes the most recent session.
6040
+ Use a full session ID or 4-char suffix.`
6041
+ },
6042
+ {
6043
+ name: "name",
6044
+ raw: `---
6045
+ name: name
6046
+ description: Tag this session with a label
6047
+ section: More
6048
+ args: [label]
6049
+ handler: ../commands/name.ts
6050
+ ---
6051
+
6052
+ Give the current session a human-readable name so you can find it
6053
+ later. The name appears in the REPL prompt, session list, and
6054
+ welcome dashboard. Max 40 characters.`
6055
+ },
6056
+ {
6057
+ name: "switch",
6058
+ raw: `---
6059
+ name: switch
6060
+ description: Jump to a named session
6061
+ section: More
6062
+ args: [name]
6063
+ handler: ../commands/switch.ts
6064
+ hidden: true
6065
+ ---
6066
+
6067
+ Save the current session and switch to a named one. If the name
6068
+ exists, loads its context and messages. If new, creates a fresh
6069
+ session with that name. Without arguments, lists all named sessions.`
6070
+ },
6071
+ {
6072
+ name: "actions",
6073
+ raw: `---
6074
+ name: actions
6075
+ description: Propose, approve, and execute actions
6076
+ section: More
6077
+ args: [list|test|show|approve|reject|execute|continue] [id]
6078
+ handler: ../commands/actions.ts
6079
+ ---
6080
+
6081
+ Create and manage action proposals. \`/actions test\` creates a local manual
6082
+ dry-run proposal that exercises the approval and execution lifecycle without
6083
+ touching external tools. Execute-class actions require local approval before
6084
+ they can run. Use \`/actions continue\` to advance the newest pending or
6085
+ approved proposal without copying a handle during the active workflow.`
6086
+ },
6087
+ {
6088
+ name: "diagnose",
6089
+ raw: `---
6090
+ name: diagnose
6091
+ description: Compute vital signs and generate findings
6092
+ section: Hidden
6093
+ hidden: true
6094
+ args: [--deep] [--segment <name>]
6095
+ handler: ../commands/diagnose.ts
6096
+ ---
6097
+
6098
+ Compute the 5 vital signs (freshness, flow rate, drop rate, signal-to-noise,
6099
+ thread depth) for either the full dataset or a segment. Companion to \`/metrics\`
6100
+ when your session primary is SaaS metrics. Use \`--deep\` to run the agentic
6101
+ investigation loop instead of the single-shot findings path.`
6102
+ },
6103
+ {
6104
+ name: "metrics",
6105
+ raw: `---
6106
+ name: metrics
6107
+ description: SaaS metrics \u2014 refresh or add the revenue view
6108
+ section: Hidden
6109
+ hidden: true
6110
+ args: [--findings] [--segment <name>]
6111
+ handler: ../commands/metrics.ts
6112
+ ---
6113
+
6114
+ Compute SaaS revenue metrics from pipeline or revenue-ledger data: ARR, NRR/GRR,
6115
+ Win Rate, Pipeline Coverage, and more. Each metric includes a confidence score
6116
+ and reliability gate showing what data unlocks the next tier. Use \`--findings\`
6117
+ for AI analysis calibrated to your company profile. Revenue ledger CSV format:
6118
+ account, period, mrr, event_type.`
6119
+ },
6120
+ {
6121
+ name: "ask",
6122
+ raw: `---
6123
+ name: ask
6124
+ description: Chat with your pipeline data
6125
+ section: Hidden
6126
+ hidden: true
6127
+ args: <question>
6128
+ handler: ../commands/ask.ts
6129
+ ---
6130
+
6131
+ Ask a plain-English question about your GTM health and SaaS metrics. Free-form
6132
+ text at the REPL prompt routes to the same agent. Respects your session primary
6133
+ lens; can cross-reference vital signs and revenue metrics via tools.`
6134
+ },
6135
+ {
6136
+ name: "recap",
6137
+ raw: `---
6138
+ name: recap
6139
+ description: Summarize the current session
6140
+ section: More
6141
+ handler: ../commands/recap.ts
6142
+ ---
6143
+
6144
+ Summarize the current REPL session using AI. Reads all natural-language
6145
+ exchanges from the session and produces a structured overview: key findings,
6146
+ dollar impacts, and recommended next steps.`
6147
+ },
6148
+ {
6149
+ name: "remember",
6150
+ raw: `---
6151
+ name: remember
6152
+ description: Teach the analyst a durable fact
6153
+ section: More
6154
+ args: <fact> | decision: <text> | preference: <text>
6155
+ handler: ../commands/remember.ts
6156
+ ---
6157
+
6158
+ Store a durable fact, decision, or preference about your business. Stored
6159
+ memory flows into every future analysis so the agent gets to know your
6160
+ business better over time \u2014 like a consultant building up a client file.`
6161
+ },
6162
+ {
6163
+ name: "recall",
6164
+ raw: `---
6165
+ name: recall
6166
+ description: See what the analyst remembers
6167
+ section: More
6168
+ args: [topic]
6169
+ handler: ../commands/recall.ts
6170
+ ---
6171
+
6172
+ Jog the analyst's memory. With no arguments, lists the durable facts it knows
6173
+ and the analyses it has already run. Pass a topic to see what it remembers
6174
+ about that subject \u2014 pulled from facts, strategies, wins, and ingested
6175
+ knowledge.`
6176
+ },
6177
+ {
6178
+ name: "rate",
6179
+ raw: `---
6180
+ name: rate
6181
+ description: Give feedback on the last answer
6182
+ section: More
6183
+ args: good [note] | bad <note>
6184
+ handler: ../commands/rate.ts
6185
+ ---
6186
+
6187
+ Tell the analyst how its last answer landed. \`/rate good\` reinforces the
6188
+ approach; \`/rate bad <what was off>\` records a correction. Feedback becomes a
6189
+ durable preference so the analyst gets better at working with you over time.`
6190
+ },
6191
+ {
6192
+ name: "knowledge",
6193
+ raw: `---
6194
+ name: knowledge
6195
+ description: Ingest external case studies & frameworks
6196
+ section: More
6197
+ args: [add <file> | list]
6198
+ handler: ../commands/knowledge.ts
6199
+ ---
6200
+
6201
+ Teach the analyst from work done outside the platform. \`/knowledge add <file>\`
6202
+ ingests a markdown, text, or PDF case study, framework, or benchmark report and
6203
+ indexes it for retrieval during analysis. \`/knowledge list\` shows what's
6204
+ indexed. Drop files into ~/.ntrp/knowledge to stage them.`
6205
+ },
6206
+ {
6207
+ name: "ingest",
6208
+ raw: `---
6209
+ name: ingest
6210
+ description: Import CRM CSV exports (or --demo)
6211
+ section: Hidden
6212
+ hidden: true
6213
+ args: <file> | --demo [--scenario <name>]
6214
+ handler: ../commands/ingest.ts
6215
+ ---
6216
+
6217
+ Import a CSV file from your CRM (Salesforce, HubSpot, Outreach). The command
6218
+ auto-detects the entity type based on column headers and runs identity
6219
+ resolution after import.
6220
+
6221
+ Pass \`--demo\` instead of a file to generate a synthetic dataset shaped by
6222
+ your company profile. Accepts \`--scenario <name>\` to pick a scenario (else
6223
+ random) and \`--regen-taxonomy\` to rebuild the profile-derived market
6224
+ taxonomy. Rep names draw from a curated music / sports / film roster for a
6225
+ little demo delight; pass \`--no-whimsy\` to use generic names instead.`
6226
+ },
6227
+ {
6228
+ name: "demo",
6229
+ raw: `---
6230
+ name: demo
6231
+ description: Generate demo scenario data
6232
+ section: Getting Started
6233
+ args: [--scenario <name>] [--regen-taxonomy] [--no-whimsy]
6234
+ handler: ../commands/generate.ts
6235
+ hidden: true
6236
+ ---
6237
+
6238
+ Generate a complete dataset for one of 5 demo scenarios: hidden_crisis,
6239
+ leaky_bucket, stale_pipeline, lone_wolf, busy_bees. Without \`--scenario\`,
6240
+ picks one at random each run. Use \`--list-scenarios\` to see descriptions.
6241
+ Use \`--regen-taxonomy\` to force a fresh AI-built market taxonomy.
6242
+ By default, sales rep names are drawn from a curated music / sports / film
6243
+ roster; pass \`--no-whimsy\` for generic placeholder names.
6244
+
6245
+ This command is hidden \u2014 prefer \`/ingest --demo\` which delegates here.`
6246
+ },
6247
+ {
6248
+ name: "strategy",
6249
+ raw: `---
6250
+ name: strategy
6251
+ description: Ingest and manage GTM strategies
6252
+ section: More
6253
+ args: [list|ingest|add|show|sync|sources] [file|text|id|folder]
6254
+ handler: ../commands/strategy.ts
6255
+ ---
6256
+
6257
+ Add user-authored or agent-authored GTM strategies to NTRP's strategy library.
6258
+ \`/strategy ingest <file>\` accepts markdown, YAML, PDF, or plain text files.
6259
+ \`/strategy ingest -\` reads strategy text from stdin, and \`/strategy add "..."\`
6260
+ creates a strategy from a short pasted description.
6261
+ \`/strategy sync --path <folder>\` scans an Obsidian-style local library and
6262
+ imports supported strategy documents. Use \`/strategy sources\` to see available
6263
+ library connector types.`
6264
+ },
6265
+ {
6266
+ name: "segment",
6267
+ raw: `---
6268
+ name: segment
6269
+ description: Browse and inspect segments
6270
+ section: More
6271
+ args: [list|show|compare|create|delete] [args]
6272
+ handler: ../commands/segment.ts
6273
+ ---
6274
+
6275
+ Browse, inspect, and manage data segments. With no arguments, lists all
6276
+ segments sorted worst-first. Subcommands: \`show <name>\`, \`compare <a> <b>\`,
6277
+ \`create <name> --entity <type> --filter <expr>\`, \`delete <name>\`.`
6278
+ },
6279
+ {
6280
+ name: "report",
6281
+ raw: `---
6282
+ name: report
6283
+ description: Export latest diagnosis
6284
+ section: More
6285
+ args: [--format terminal|md|json] [--output <file>]
6286
+ handler: ../commands/report.ts
6287
+ ---
6288
+
6289
+ Export the most recent diagnosis as terminal output, markdown, or JSON. Use
6290
+ \`--output <file>\` to write to disk instead of stdout.`
6291
+ },
6292
+ {
6293
+ name: "progress",
6294
+ raw: `---
6295
+ name: progress
6296
+ description: Usage stats and milestone ladder
6297
+ section: Navigation
6298
+ args: [reset] [--confirm]
6299
+ handler: ../commands/progress.ts
6300
+ ---
6301
+
6302
+ Hours saved, weekly activity trend, session counts, AI token usage, and the
6303
+ full milestone ladder with progress bars. Use reset (type "reset" to confirm)
6304
+ to clear hours and milestones while keeping this install's identity.`
6305
+ },
6306
+ {
6307
+ name: "status",
6308
+ raw: `---
6309
+ name: status
6310
+ description: Show last diagnosis and entity counts
6311
+ section: More
6312
+ handler: ../commands/status.ts
6313
+ ---
6314
+
6315
+ Show what data you currently have loaded and the result of your last
6316
+ diagnosis, if any.`
6317
+ },
6318
+ {
6319
+ name: "scratch",
6320
+ raw: `---
6321
+ name: scratch
6322
+ description: Wipe config, profile, and all datasets
6323
+ section: Admin
6324
+ args: [--confirm] [--include-progress]
6325
+ handler: ../commands/scratch.ts
6326
+ hidden: true
6327
+ ---
6328
+
6329
+ Minimal factory reset: removes API key, config, company profile, all sessions,
6330
+ per-session datasets, and demo taxonomy cache. Preserves progress (hours saved)
6331
+ by default. Pass \`--include-progress\` to also wipe install identity and hours.
6332
+ Also preserves memory, strategies, wins, knowledge, exports, and audit. Requires
6333
+ typing \`scratch\` in the REPL or passing \`--confirm\` one-shot. Triggers
6334
+ onboarding on next interactive use.`
6335
+ },
6336
+ {
6337
+ name: "cleanup",
6338
+ raw: `---
6339
+ name: cleanup
6340
+ description: Close all active sessions
6341
+ section: Admin
6342
+ args: [--confirm]
6343
+ handler: ../commands/cleanup.ts
6344
+ hidden: true
6345
+ ---
6346
+
6347
+ Mark every in-progress session as ended without deleting transcripts or dataset
6348
+ files. Interactive REPL only. Confirm with y/N or \`--confirm\` one-shot.`
6349
+ },
6350
+ {
6351
+ name: "deactivate-demo",
6352
+ raw: `---
6353
+ name: deactivate-demo
6354
+ description: Disable demo data generators
6355
+ section: Admin
6356
+ args: [--confirm]
6357
+ handler: ../commands/deactivate-demo.ts
6358
+ hidden: true
6359
+ ---
6360
+
6361
+ Persistently disable demo generators (\`/ingest --demo\`, \`/new --demo\`, NL
6362
+ "use demo data"). Re-enable with \`/config set demo-enabled true\`.`
6363
+ },
6364
+ {
6365
+ name: "reset",
6366
+ raw: `---
6367
+ name: reset
6368
+ description: Clear all data and start fresh
6369
+ section: More
6370
+ args: [--force]
6371
+ handler: ../commands/reset.ts
6372
+ ---
6373
+
6374
+ Drop all rows from every table in the local DuckDB database. Requires
6375
+ \`--force\` to proceed.`
6376
+ },
6377
+ {
6378
+ name: "playbook",
6379
+ raw: `---
6380
+ name: playbook
6381
+ description: Show or extend recommended plays
6382
+ section: More
6383
+ args: [--vital-sign <name>] [play-id] | add
6384
+ handler: ../commands/playbook.ts
6385
+ ---
6386
+
6387
+ Show the playbook of recommended plays keyed to each vital sign. Pass a
6388
+ play-id to drill into a single play's steps and expected outcome. Run
6389
+ \`/playbook add\` and the analyst walks you through capturing a new play, step by
6390
+ step \u2014 no flags or quoting needed. Learned plays become recommendable during
6391
+ analysis. (Power users can still pass everything as flags in one shot.)`
6392
+ },
6393
+ {
6394
+ name: "export",
6395
+ raw: `---
6396
+ name: export
6397
+ description: Save diagnosis to Obsidian notes
6398
+ section: More
6399
+ args: [--dir <path>] [--segment <name>]
6400
+ handler: ../commands/export.ts
6401
+ ---
6402
+
6403
+ Write the most recent diagnosis to your configured notes directory as
6404
+ markdown, ready for Obsidian, Logseq, or any other note tool.`
6405
+ },
6406
+ {
6407
+ name: "publish",
6408
+ raw: `---
6409
+ name: publish
6410
+ description: Preview and propose repository exports
6411
+ section: More
6412
+ args: [preview|propose|targets] [--target markdown] [--dir <path>]
6413
+ handler: ../commands/publish.ts
6414
+ ---
6415
+
6416
+ Build a full repository export package from the latest diagnosis, findings,
6417
+ strategies, evidence, and action receipts. \`preview\` shows the write plan;
6418
+ \`propose\` creates an approval-gated action proposal. The first executable
6419
+ target is local markdown for Obsidian-compatible repositories. Notion,
6420
+ Airtable, and GitHub mappings are documented via \`/publish targets\`.`
6421
+ },
6422
+ {
6423
+ name: "backmeup",
6424
+ raw: `---
6425
+ name: backmeup
6426
+ description: Export diagnosis receipts as CSV
6427
+ section: More
6428
+ args: [--output <dir>]
6429
+ handler: ../commands/backmeup.ts
6430
+ ---
6431
+
6432
+ Export your latest diagnosis as a folder of CSV files you can attach to a
6433
+ Slack thread, email, or slide deck. Creates a timestamped folder under
6434
+ ~/.ntrp/exports/ containing a cover sheet with headline numbers, a findings
6435
+ file, and per-vital-sign evidence CSVs showing exactly which deals, contacts,
6436
+ or orgs drove each score. Use --output <dir> to write somewhere else.`
6437
+ },
6438
+ {
6439
+ name: "profile",
6440
+ raw: `---
6441
+ name: profile
6442
+ description: Set sales motion
6443
+ section: Settings
6444
+ args: [list|set|show] [preset]
6445
+ handler: ../commands/profile.ts
6446
+ ---
6447
+
6448
+ Choose a sales motion preset (PLG, SMB Velocity, Mid-Market, Enterprise). Each
6449
+ preset adjusts the vital-sign thresholds to match your deal cycle.`
6450
+ },
6451
+ {
6452
+ name: "connect",
6453
+ raw: `---
6454
+ name: connect
6455
+ description: Connect an AI provider (paste any key)
6456
+ section: Settings
6457
+ args: [provider] [--key <key>] [--base-url <url> --id <name>]
6458
+ handler: ../commands/connect.ts
6459
+ ---
6460
+
6461
+ Paste any provider's API key \u2014 NTRP identifies the provider from the key
6462
+ format (probing ambiguous ones), validates it, discovers which models the key
6463
+ can use, and builds the HIGH/MEDIUM/LOW tier stack automatically.
6464
+
6465
+ Works with Anthropic, OpenAI, Google Gemini, Groq, Mistral, DeepSeek, xAI,
6466
+ OpenRouter, Together, and Fireworks out of the box. \`/connect ollama\` wires a
6467
+ local Ollama; \`/connect --base-url <url> --id <name>\` registers any other
6468
+ OpenAI-compatible endpoint.`
6469
+ },
6470
+ {
6471
+ name: "config",
6472
+ raw: `---
6473
+ name: config
6474
+ description: Get/set config values
6475
+ section: Settings
6476
+ args: [get|set|list|delete] <key> [value]
6477
+ handler: ../commands/config.ts
6478
+ ---
6479
+
6480
+ Manage CLI configuration stored at \`~/.ntrp/config.json\`. Useful keys:
6481
+ \`api-key\` (Anthropic), \`openai-api-key\` (and \`groq-api-key\`, \`google-api-key\`, ...),
6482
+ \`llm-primary\` (default engine), \`llm-tier\`, \`llm-auto-failover\`,
6483
+ \`default-format\`, \`export-dir\`.
6484
+
6485
+ Setting a provider key opens a hidden prompt and auto-discovers that
6486
+ provider's models. Prefer \`/connect\` \u2014 it detects the provider for you.`
6487
+ },
6488
+ {
6489
+ name: "provider",
6490
+ raw: `---
6491
+ name: provider
6492
+ description: Switch active LLM engine
6493
+ section: Settings
6494
+ args: [<id>|list|reset|save|failover on|off]
6495
+ handler: ../commands/provider.ts
6496
+ ---
6497
+
6498
+ Choose which connected engine answers this session \u2014 any provider added via
6499
+ \`/connect\` (anthropic, openai, groq, google, ollama, custom endpoints, ...).
6500
+ Session-scoped by default; \`/provider save\` writes the default to config.
6501
+ \`/provider failover on\` enables rate-limit auto-failover.`
6502
+ },
6503
+ {
6504
+ name: "tier",
6505
+ raw: `---
6506
+ name: tier
6507
+ description: Set inference tier (HIGH/MEDIUM/LOW)
6508
+ section: Settings
6509
+ args: [high|medium|low|list] [--default]
6510
+ handler: ../commands/tier.ts
6511
+ ---
6512
+
6513
+ Set quality/cost tier for this REPL session. Agentic surfaces respect your tier;
6514
+ some single-shot surfaces keep fixed defaults. \`/tier list\` highlights the
6515
+ active stack. Add \`--default\` to persist to config.`
6516
+ },
6517
+ {
6518
+ name: "model",
6519
+ raw: `---
6520
+ name: model
6521
+ description: Override the active LLM model
6522
+ section: Settings
6523
+ args: [list|set <id>|refresh|clear] [--default]
6524
+ handler: ../commands/model.ts
6525
+ ---
6526
+
6527
+ \`/model list\` shows the models discovered for the active engine with their
6528
+ tier assignments. \`/model refresh\` re-discovers the live list. \`/model set <id>\`
6529
+ pins a model on the **active engine**; cross-provider IDs are rejected \u2014
6530
+ switch with \`/provider\` first.`
6531
+ },
6532
+ {
6533
+ name: "activate",
6534
+ raw: `---
6535
+ name: activate
6536
+ description: Enter license key
6537
+ section: Settings
6538
+ args: <license>
6539
+ handler: ../commands/activate.ts
6540
+ ---
6541
+
6542
+ Activate NTRP with your license key (format: NTRP-XXXX-XXXX-XXXX). Most
6543
+ commands require a valid license.`
6544
+ },
6545
+ {
6546
+ name: "upgrade",
6547
+ raw: `---
6548
+ name: upgrade
6549
+ description: Upgrade trial to Pro \u2014 checkout + paste key
6550
+ section: Settings
6551
+ handler: ../commands/upgrade.ts
6552
+ ---
6553
+
6554
+ Open the Pro checkout page and paste your new license key without leaving
6555
+ the REPL. Use during trial grace or after cutoff. Flags: --url (print checkout URL only).`
6556
+ },
6557
+ {
6558
+ name: "checkout",
6559
+ raw: `---
6560
+ name: checkout
6561
+ description: Open signup checkout in your browser
6562
+ section: Settings
6563
+ handler: ../commands/checkout.ts
6564
+ ---
6565
+
6566
+ Opens the Lemon Squeezy checkout page in your default browser. Use anytime
6567
+ you need a trial or Pro license key.`
6568
+ },
6569
+ {
6570
+ name: "feedback",
6571
+ raw: `---
6572
+ name: feedback
6573
+ description: Correct your profile in plain English
6574
+ section: Settings
6575
+ args: <correction>
6576
+ handler: ../commands/feedback.ts
6577
+ ---
6578
+
6579
+ Apply natural-language corrections to your company profile. Maps structured
6580
+ fields when possible (e.g. "our sales cycle is 6 months" updates
6581
+ sales_cycle_days) and merges remaining nuances into a custom_context
6582
+ paragraph that flows into all AI surfaces.`
6583
+ }
6584
+ ];
6585
+ }
6586
+ });
6587
+
5858
6588
  // src/ai/prompt-parts.ts
5859
6589
  function buildCompanyProfileBlock() {
5860
6590
  const p = loadProfile();
@@ -5881,12 +6611,41 @@ function buildPlaybookBlock() {
5881
6611
  Learned plays (added from this team's experience and ingested case studies \u2014 recommend these when they fit):
5882
6612
  ${learned}`;
5883
6613
  }
5884
- var ANALYST_INSTINCT_BLOCK, VITAL_SIGNS_BLOCK, PLAYBOOK_BLOCK, METRICS_BLOCK, FINDINGS_SCHEMA_BLOCK;
6614
+ function buildCommandCatalogBlock() {
6615
+ const GROUP_ANALYSIS = "Analysis & data";
6616
+ const GROUP_SESSION = "Session, memory & outputs";
6617
+ const GROUP_SETTINGS = "Settings & providers";
6618
+ const groupOrder = [GROUP_ANALYSIS, GROUP_SESSION, GROUP_SETTINGS];
6619
+ const groupFor = (section) => {
6620
+ if (section === "Hidden" || section === "Getting Started") return GROUP_ANALYSIS;
6621
+ if (section === "Settings") return GROUP_SETTINGS;
6622
+ return GROUP_SESSION;
6623
+ };
6624
+ const groups = new Map(groupOrder.map((label) => [label, []]));
6625
+ for (const meta of listWorkflows(true)) {
6626
+ if (meta.name === "ask") continue;
6627
+ if (meta.section === "Admin") continue;
6628
+ groups.get(groupFor(meta.section)).push(formatCatalogLine(meta));
6629
+ }
6630
+ groups.get(GROUP_SESSION).push(
6631
+ "- /help \u2014 Show the shortcut list",
6632
+ "- /home \u2014 Show the welcome dashboard and current session status"
6633
+ );
6634
+ return groupOrder.filter((label) => groups.get(label).length > 0).map((label) => `${label}:
6635
+ ${groups.get(label).join("\n")}`).join("\n\n");
6636
+ }
6637
+ function formatCatalogLine(meta) {
6638
+ const args = meta.args?.trim() ? ` ${meta.args.trim()}` : "";
6639
+ const note = DESTRUCTIVE_COMMAND_NOTES[meta.name] ? ` (${DESTRUCTIVE_COMMAND_NOTES[meta.name]})` : "";
6640
+ return `- /${meta.name}${args} \u2014 ${meta.description}${note}`;
6641
+ }
6642
+ var ANALYST_INSTINCT_BLOCK, VITAL_SIGNS_BLOCK, PLAYBOOK_BLOCK, DESTRUCTIVE_COMMAND_NOTES, METRICS_BLOCK, FINDINGS_SCHEMA_BLOCK;
5885
6643
  var init_prompt_parts = __esm({
5886
6644
  "src/ai/prompt-parts.ts"() {
5887
6645
  "use strict";
5888
6646
  init_profile();
5889
6647
  init_playbook();
6648
+ init_registry();
5890
6649
  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.
5891
6650
 
5892
6651
  - 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.)
@@ -5909,6 +6668,9 @@ Restraint matters: NTRP is a stethoscope, not a surgeon. Observe, connect, and r
5909
6668
  - "Fix the Handoff Gap" (id: fix-handoff-gap) \u2014 when drop_rate is high
5910
6669
  - "Retarget Misdirected Effort" (id: retarget-effort) \u2014 when signal_to_noise is low
5911
6670
  - "Unstick the Pipeline" (id: unstick-pipeline) \u2014 when flow_rate is low`;
6671
+ DESTRUCTIVE_COMMAND_NOTES = {
6672
+ reset: "destructive \u2014 wipes all data, requires --force"
6673
+ };
5912
6674
  METRICS_BLOCK = `Revenue metrics measure GTM output \u2014 the standard SaaS metrics:
5913
6675
  - ARR: Total closed-won revenue. New ARR + Expansion ARR = growth; Churned + Contraction = leakage.
5914
6676
  - NRR (Net Revenue Retention): >100% means growing from existing customers. Best-in-class: 110%+.
@@ -13800,15 +14562,18 @@ function inferHandoffTarget(input) {
13800
14562
  return "plan";
13801
14563
  }
13802
14564
  function isShipIntent(input) {
13803
- return /\b(handoff|ship|deliver|board memo|export|turn this into|write.?up|action plan)\b/i.test(
13804
- input.trim()
13805
- );
14565
+ const line = input.trim();
14566
+ if (/\?\s*$/.test(line) || QUESTION_LEAD_RE.test(line)) return false;
14567
+ return SHIP_INTENT_RE.test(line);
13806
14568
  }
14569
+ var QUESTION_LEAD_RE, SHIP_INTENT_RE;
13807
14570
  var init_handoff_draft = __esm({
13808
14571
  "src/conversation/handoff-draft.ts"() {
13809
14572
  "use strict";
13810
14573
  init_profile();
13811
14574
  init_session_analysis();
14575
+ 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;
14576
+ 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;
13812
14577
  }
13813
14578
  });
13814
14579
 
@@ -14370,6 +15135,16 @@ FORMATTING (brief mode \u2014 scannable, not chunky):
14370
15135
  - No ### headings, no tables, no --- footer, no closing "want me to dig deeper?" question.
14371
15136
  - Single-point answers can stay one sentence \u2014 don't force bullets when one line is enough.
14372
15137
  `;
15138
+ 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`.";
15139
+ 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):
15140
+ ${buildCommandCatalogBlock()}
15141
+
15142
+ COMMAND SUGGESTION RULES (suggest-only):
15143
+ - 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.
15144
+ - 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\`.
15145
+ - 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.
15146
+ - 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).
15147
+ ${commandStyleRule}`;
14373
15148
  const outputRules = responseMode === "brief" ? `OUTPUT RULES:
14374
15149
  - Respond in plain text markdown (not JSON). Lead sentence + bullets when helpful \u2014 never a dense paragraph wall.
14375
15150
  - Pull numbers from the completed session analysis or conversation history \u2014 never guess.
@@ -14393,6 +15168,8 @@ ${VITAL_SIGNS_BLOCK}
14393
15168
 
14394
15169
  PLAYBOOK \u2014 recommend these plays when appropriate:
14395
15170
  ${buildPlaybookBlock()}
15171
+
15172
+ ${commandSection}
14396
15173
  ${formattingSection}
14397
15174
  ${outputRules}`;
14398
15175
  }
@@ -14651,56 +15428,132 @@ var init_diagnosis = __esm({
14651
15428
  }
14652
15429
  });
14653
15430
 
14654
- // src/ai/strategy-normalize.ts
14655
- function stripFences2(text) {
14656
- const trimmed = text.trim();
14657
- const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
14658
- if (fenced) return fenced[1].trim();
14659
- return trimmed;
14660
- }
14661
- async function normalizeStrategy(input, ctx) {
14662
- assertReplAi(ctx);
14663
- const playbookBlock = getPlaybook().map((play) => `- ${play.id}: ${play.name} (${play.trigger_vital_sign}) \u2014 ${play.why}`).join("\n");
14664
- const { text } = await llmCompleteText(
14665
- "strategy",
14666
- SYSTEM_PROMPT2,
14667
- `AVAILABLE PLAYBOOK IDS:
14668
- ${playbookBlock}
14669
-
14670
- SOURCE TYPE: ${input.source_type}
14671
- SOURCE PATH: ${input.source_path ?? "none"}
14672
- STRUCTURED HINT:
14673
- ${JSON.stringify(input.structured_hint ?? {}, null, 2)}
14674
-
14675
- RAW STRATEGY DOCUMENT:
14676
- ${input.raw_text.slice(0, 16e3)}
14677
-
14678
- Normalize this strategy now as strict JSON.`,
14679
- 1800,
14680
- ctx
14681
- );
14682
- let parsed;
14683
- try {
14684
- parsed = JSON.parse(stripFences2(text));
14685
- } catch {
14686
- throw new Error("AI response is not valid strategy JSON");
15431
+ // src/services/report.ts
15432
+ async function loadReportData(segmentName) {
15433
+ await initSchema();
15434
+ const diagnosis = await loadLatestDiagnosis();
15435
+ if (!diagnosis) {
15436
+ throw new NtrpError("no_diagnosis", "No diagnosis data found. Run diagnose first.", 4 /* NoData */);
14687
15437
  }
14688
- return validateStrategyDraft(parsed, input);
14689
- }
14690
- function validateStrategyDraft(raw, input) {
14691
- const title = stringValue(raw.title) ?? "Untitled Strategy";
14692
- const goal = stringValue(raw.goal) ?? "Clarify the GTM strategy and define measurable outcomes.";
14693
- const hypothesis = stringValue(raw.hypothesis) ?? "If the team executes this strategy consistently, the targeted GTM health metrics should improve.";
14694
- const targetSegment = stringValue(raw.target_segment) ?? "Unspecified segment";
14695
- return {
14696
- title,
14697
- status: enumValue(raw.status, ["draft", "active", "paused", "completed", "archived"], "draft"),
14698
- source_type: input.source_type,
14699
- source_path: input.source_path,
14700
- goal,
14701
- hypothesis,
14702
- target_segment: targetSegment,
14703
- priority: enumValue(raw.priority, ["low", "medium", "high"], "medium"),
15438
+ let { health, segments, findings, entityCounts } = diagnosis;
15439
+ let scopedSegment = null;
15440
+ if (segmentName) {
15441
+ const lower = segmentName.toLowerCase();
15442
+ let match = segments.find((s) => s.segment.name.toLowerCase() === lower);
15443
+ if (!match) {
15444
+ const subs = segments.filter((s) => s.segment.name.toLowerCase().includes(lower));
15445
+ if (subs.length === 1) match = subs[0];
15446
+ else if (subs.length > 1) {
15447
+ throw new NtrpError("ambiguous_segment", `"${segmentName}" matches multiple segments.`, 2 /* Usage */, {
15448
+ matches: subs.map((s) => s.segment.name)
15449
+ });
15450
+ }
15451
+ }
15452
+ if (!match) {
15453
+ throw new NtrpError("segment_not_found", `No segment matching "${segmentName}".`, 2 /* Usage */, {
15454
+ available_segments: segments.map((s) => s.segment.name)
15455
+ });
15456
+ }
15457
+ scopedSegment = match;
15458
+ health = match.result;
15459
+ segments = [match];
15460
+ findings = findings.filter((f) => f.segment.toLowerCase().includes(lower));
15461
+ }
15462
+ return {
15463
+ diagnosis,
15464
+ scopedSegment,
15465
+ reportData: {
15466
+ health,
15467
+ segments,
15468
+ findings,
15469
+ entityCounts,
15470
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
15471
+ }
15472
+ };
15473
+ }
15474
+ var init_report = __esm({
15475
+ "src/services/report.ts"() {
15476
+ "use strict";
15477
+ init_schema();
15478
+ init_queries();
15479
+ init_errors2();
15480
+ init_types2();
15481
+ }
15482
+ });
15483
+
15484
+ // src/ai/explore-mode.ts
15485
+ function isDeepDiveQuestion(question) {
15486
+ return DEEP_DIVE_PATTERNS.some((p) => p.test(question.trim()));
15487
+ }
15488
+ function resolveExploreResponseMode(question, ctx, priorTurnCount) {
15489
+ if (isDeepDiveQuestion(question)) return "deep";
15490
+ if (isAnalysisReady(ctx)) return "brief";
15491
+ if (ctx.stage === "analyzed" || ctx.analysis.completed.length > 0) return "brief";
15492
+ if (priorTurnCount === 0) return "deep";
15493
+ return "brief";
15494
+ }
15495
+ var DEEP_DIVE_PATTERNS;
15496
+ var init_explore_mode = __esm({
15497
+ "src/ai/explore-mode.ts"() {
15498
+ "use strict";
15499
+ init_context2();
15500
+ DEEP_DIVE_PATTERNS = [
15501
+ /\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,
15502
+ /\b(how many|which deals|which accounts|who owns|top \d+|every deal|all stuck)\b/i
15503
+ ];
15504
+ }
15505
+ });
15506
+
15507
+ // src/ai/strategy-normalize.ts
15508
+ function stripFences2(text) {
15509
+ const trimmed = text.trim();
15510
+ const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
15511
+ if (fenced) return fenced[1].trim();
15512
+ return trimmed;
15513
+ }
15514
+ async function normalizeStrategy(input, ctx) {
15515
+ assertReplAi(ctx);
15516
+ const playbookBlock = getPlaybook().map((play) => `- ${play.id}: ${play.name} (${play.trigger_vital_sign}) \u2014 ${play.why}`).join("\n");
15517
+ const { text } = await llmCompleteText(
15518
+ "strategy",
15519
+ SYSTEM_PROMPT2,
15520
+ `AVAILABLE PLAYBOOK IDS:
15521
+ ${playbookBlock}
15522
+
15523
+ SOURCE TYPE: ${input.source_type}
15524
+ SOURCE PATH: ${input.source_path ?? "none"}
15525
+ STRUCTURED HINT:
15526
+ ${JSON.stringify(input.structured_hint ?? {}, null, 2)}
15527
+
15528
+ RAW STRATEGY DOCUMENT:
15529
+ ${input.raw_text.slice(0, 16e3)}
15530
+
15531
+ Normalize this strategy now as strict JSON.`,
15532
+ 1800,
15533
+ ctx
15534
+ );
15535
+ let parsed;
15536
+ try {
15537
+ parsed = JSON.parse(stripFences2(text));
15538
+ } catch {
15539
+ throw new Error("AI response is not valid strategy JSON");
15540
+ }
15541
+ return validateStrategyDraft(parsed, input);
15542
+ }
15543
+ function validateStrategyDraft(raw, input) {
15544
+ const title = stringValue(raw.title) ?? "Untitled Strategy";
15545
+ const goal = stringValue(raw.goal) ?? "Clarify the GTM strategy and define measurable outcomes.";
15546
+ const hypothesis = stringValue(raw.hypothesis) ?? "If the team executes this strategy consistently, the targeted GTM health metrics should improve.";
15547
+ const targetSegment = stringValue(raw.target_segment) ?? "Unspecified segment";
15548
+ return {
15549
+ title,
15550
+ status: enumValue(raw.status, ["draft", "active", "paused", "completed", "archived"], "draft"),
15551
+ source_type: input.source_type,
15552
+ source_path: input.source_path,
15553
+ goal,
15554
+ hypothesis,
15555
+ target_segment: targetSegment,
15556
+ priority: enumValue(raw.priority, ["low", "medium", "high"], "medium"),
14704
15557
  linked_play_ids: validPlayIds(raw.linked_play_ids),
14705
15558
  success_metrics: metricArray(raw.success_metrics),
14706
15559
  leading_indicators: metricArray(raw.leading_indicators),
@@ -15261,1117 +16114,1062 @@ var init_strategy = __esm({
15261
16114
  }
15262
16115
  });
15263
16116
 
15264
- // src/ai/embeddings.ts
15265
- var embeddings_exports = {};
15266
- __export(embeddings_exports, {
15267
- embedItems: () => embedItems,
15268
- embedText: () => embedText,
15269
- isEmbeddingsEnabled: () => isEmbeddingsEnabled
15270
- });
15271
- function resolveConfig() {
15272
- const explicit = getConfigValue("embeddings-provider");
15273
- if (explicit === "none") return null;
15274
- const voyageKey = process.env.VOYAGE_API_KEY ?? getConfigValue("voyage-api-key");
15275
- const openaiKey = getOpenAiApiKey();
15276
- const provider = explicit ?? (voyageKey ? "voyage" : openaiKey ? "openai" : "none");
15277
- if (provider === "voyage" && voyageKey) {
15278
- return { provider, apiKey: voyageKey, model: VOYAGE_MODEL, url: "https://api.voyageai.com/v1/embeddings" };
15279
- }
15280
- if (provider === "openai" && openaiKey) {
15281
- return { provider, apiKey: openaiKey, model: OPENAI_MODEL, url: "https://api.openai.com/v1/embeddings" };
15282
- }
15283
- return null;
15284
- }
15285
- function isEmbeddingsEnabled() {
15286
- return resolveConfig() !== null;
15287
- }
15288
- async function callProvider(texts) {
15289
- const cfg = resolveConfig();
15290
- if (!cfg || texts.length === 0) return null;
15291
- try {
15292
- const res = await fetch(cfg.url, {
15293
- method: "POST",
15294
- headers: {
15295
- "Content-Type": "application/json",
15296
- Authorization: `Bearer ${cfg.apiKey}`
15297
- },
15298
- body: JSON.stringify({ input: texts, model: cfg.model })
15299
- });
15300
- if (!res.ok) return null;
15301
- const json = await res.json();
15302
- if (!json.data) return null;
15303
- return json.data.map((d) => d.embedding);
15304
- } catch {
15305
- return null;
16117
+ // src/repositories/bundle.ts
16118
+ async function buildRepositoryExportPackage(options) {
16119
+ await initSchema();
16120
+ const bundle = await loadSessionAnalysisBundle();
16121
+ const diagnosis = bundle.diagnosis;
16122
+ if (!diagnosis) {
16123
+ if (bundle.metrics) {
16124
+ throw new NtrpError(
16125
+ "diagnosis_required",
16126
+ "Publish packages require a GTM health snapshot. Run /diagnose (companion) or /handoff report for metrics-only export.",
16127
+ 4 /* NoData */
16128
+ );
16129
+ }
16130
+ if (!hasAnyAnalysis(bundle)) {
16131
+ throw new NtrpError("diagnosis_required", "No analysis data found. Run /new, /diagnose, or /metrics first.", 4 /* NoData */);
16132
+ }
16133
+ throw new NtrpError("diagnosis_required", "No diagnosis data found. Run /diagnose first.", 4 /* NoData */);
15306
16134
  }
15307
- }
15308
- async function embedText(text) {
15309
- const key = text.trim();
15310
- if (!key) return null;
15311
- const cached2 = cache.get(key);
15312
- if (cached2) return cached2;
15313
- const result = await callProvider([key]);
15314
- const vec = result?.[0] ?? null;
15315
- if (vec) cache.set(key, vec);
15316
- return vec;
15317
- }
15318
- async function embedItems(items) {
15319
- const needing = [];
15320
- const out = items.map((it, index) => {
15321
- if (it.embedding && it.embedding.length > 0) return { ...it };
15322
- const cached2 = cache.get(it.text.trim());
15323
- if (cached2) return { ...it, embedding: cached2 };
15324
- needing.push({ index, text: it.text });
15325
- return { ...it };
16135
+ const strategies = await listStrategies("all");
16136
+ const strategiesWithSources = await Promise.all(
16137
+ strategies.map(async (strategy) => ({
16138
+ strategy,
16139
+ sources: await listStrategySources(strategy.id)
16140
+ }))
16141
+ );
16142
+ const proposals = await listActionProposals(100);
16143
+ const actions = await Promise.all(
16144
+ proposals.map(async (proposal) => ({
16145
+ proposal,
16146
+ executions: await listActionExecutions(proposal.id)
16147
+ }))
16148
+ );
16149
+ const sections = buildSections({
16150
+ diagnosis,
16151
+ strategiesCount: strategiesWithSources.length,
16152
+ actionsCount: actions.length
15326
16153
  });
15327
- if (needing.length === 0) return out;
15328
- const vectors = await callProvider(needing.map((n) => n.text));
15329
- if (!vectors) return out;
15330
- needing.forEach((n, i) => {
15331
- const vec = vectors[i];
15332
- if (vec) {
15333
- out[n.index].embedding = vec;
15334
- cache.set(n.text.trim(), vec);
16154
+ return {
16155
+ schema_version: "ntrp.repository_export.v1",
16156
+ export_id: uuid(),
16157
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
16158
+ target: options.target,
16159
+ summary: {
16160
+ overall_score: diagnosis.health.overall_score,
16161
+ overall_status: diagnosis.health.overall_status,
16162
+ gating_vital_sign: diagnosis.health.gating_vital_sign,
16163
+ total_value_at_risk: diagnosis.health.total_value_at_risk ?? null,
16164
+ findings_count: diagnosis.findings.length,
16165
+ strategies_count: strategiesWithSources.length,
16166
+ action_proposals_count: actions.length
16167
+ },
16168
+ diagnosis: {
16169
+ health: diagnosis.health,
16170
+ segments: diagnosis.segments.map((segment) => ({
16171
+ segment: segment.segment,
16172
+ result: segment.result
16173
+ })),
16174
+ findings: diagnosis.findings,
16175
+ entity_counts: diagnosis.entityCounts,
16176
+ upload_batch_id: diagnosis.uploadBatchId
16177
+ },
16178
+ strategies: strategiesWithSources,
16179
+ actions,
16180
+ sections,
16181
+ provenance: {
16182
+ command: options.command ?? "publish",
16183
+ model_or_fixture: options.modelOrFixture,
16184
+ source: options.source ?? "local_duckdb",
16185
+ notes: [
16186
+ "Generated from the latest persisted diagnosis.",
16187
+ "Repository writes are approval-gated through local action proposals."
16188
+ ]
15335
16189
  }
15336
- });
15337
- return out;
16190
+ };
15338
16191
  }
15339
- var VOYAGE_MODEL, OPENAI_MODEL, cache;
15340
- var init_embeddings = __esm({
15341
- "src/ai/embeddings.ts"() {
16192
+ function buildSections(input) {
16193
+ const { diagnosis } = input;
16194
+ const health = diagnosis.health;
16195
+ return [
16196
+ {
16197
+ id: "cover",
16198
+ title: "Cover Summary",
16199
+ summary: `${health.overall_score}/100 ${health.overall_status}, gated by ${health.gating_vital_sign}`,
16200
+ markdown: [
16201
+ `Overall score: **${health.overall_score}/100** (${health.overall_status})`,
16202
+ `Gating vital sign: **${VITAL_SIGN_LABELS[health.gating_vital_sign]}**`,
16203
+ `Total value at risk: **${health.total_value_at_risk ? formatCurrency(health.total_value_at_risk) : "N/A"}**`,
16204
+ `Findings: **${diagnosis.findings.length}**`,
16205
+ `Strategies: **${input.strategiesCount}**`,
16206
+ `Action proposals: **${input.actionsCount}**`
16207
+ ].join("\n\n")
16208
+ },
16209
+ {
16210
+ id: "vital-signs",
16211
+ title: "Vital Signs",
16212
+ summary: `${health.vital_signs.length} vital signs`,
16213
+ markdown: health.vital_signs.map((vs) => `- **${VITAL_SIGN_LABELS[vs.vital_sign]}:** ${Math.round(vs.score)}/100 (${vs.status}) \u2014 ${formatDollarImpact(vs.dollar_value, vs.dollar_label)}`).join("\n"),
16214
+ children: health.vital_signs.map((vs) => ({
16215
+ id: `vital-${vs.vital_sign}`,
16216
+ title: `${VITAL_SIGN_LABELS[vs.vital_sign]}: ${Math.round(vs.score)}/100`,
16217
+ summary: formatDollarImpact(vs.dollar_value, vs.dollar_label),
16218
+ markdown: [
16219
+ `Status: **${vs.status}**`,
16220
+ `Value: **${formatDollarImpact(vs.dollar_value, vs.dollar_label)}**`,
16221
+ `Flagged entities: **${vs.entity_details.length}**`,
16222
+ "",
16223
+ "Components:",
16224
+ "```json",
16225
+ JSON.stringify(vs.components, null, 2),
16226
+ "```",
16227
+ "",
16228
+ "Top entity details:",
16229
+ "```json",
16230
+ JSON.stringify(vs.entity_details.slice(0, 25), null, 2),
16231
+ "```"
16232
+ ].join("\n"),
16233
+ metadata: { vital_sign: vs.vital_sign }
16234
+ }))
16235
+ },
16236
+ {
16237
+ id: "findings",
16238
+ title: "Findings and Deep Analysis",
16239
+ summary: `${diagnosis.findings.length} findings`,
16240
+ markdown: diagnosis.findings.length > 0 ? diagnosis.findings.map((finding) => `- **${finding.severity.toUpperCase()}** ${finding.segment}: ${finding.finding}`).join("\n") : "_No findings recorded._",
16241
+ children: diagnosis.findings.map((finding, index) => ({
16242
+ id: `finding-${index + 1}`,
16243
+ title: `${finding.severity.toUpperCase()} \u2014 ${finding.segment}`,
16244
+ summary: finding.dollar_value ? formatCurrency(finding.dollar_value) : void 0,
16245
+ markdown: [
16246
+ finding.finding,
16247
+ "",
16248
+ finding.recommended_plays && finding.recommended_plays.length > 0 ? `Recommended plays: ${finding.recommended_plays.map((play) => `**${play.play_name}**`).join(", ")}` : "Recommended plays: _None recorded._",
16249
+ "",
16250
+ "Scores:",
16251
+ "```json",
16252
+ JSON.stringify(finding.vital_signs, null, 2),
16253
+ "```"
16254
+ ].join("\n")
16255
+ }))
16256
+ },
16257
+ {
16258
+ id: "segments",
16259
+ title: "Segments",
16260
+ summary: `${diagnosis.segments.length} segments`,
16261
+ markdown: diagnosis.segments.length > 0 ? diagnosis.segments.map((segment) => `- **${segment.segment.name}:** ${Math.round(segment.result.overall_score)}/100 (${segment.result.overall_status}), gated by ${segment.result.gating_vital_sign}`).join("\n") : "_No segments recorded._"
16262
+ }
16263
+ ];
16264
+ }
16265
+ var init_bundle = __esm({
16266
+ "src/repositories/bundle.ts"() {
15342
16267
  "use strict";
15343
- init_store();
15344
- init_llm_config();
15345
- VOYAGE_MODEL = "voyage-3";
15346
- OPENAI_MODEL = "text-embedding-3-small";
15347
- cache = /* @__PURE__ */ new Map();
16268
+ init_queries();
16269
+ init_session_analysis();
16270
+ init_schema();
16271
+ init_formatters();
16272
+ init_errors2();
16273
+ init_types2();
15348
16274
  }
15349
16275
  });
15350
16276
 
15351
- // src/memory/retrieval.ts
15352
- function tokenize(text) {
15353
- return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3 && !STOPWORDS.has(t));
15354
- }
15355
- function keywordRank(query, items, topK) {
15356
- const queryTokens = new Set(tokenize(query));
15357
- if (queryTokens.size === 0) {
15358
- return items.slice(-topK).reverse().map((it) => ({ id: it.id, score: 0 }));
15359
- }
15360
- const scored = items.map((it) => {
15361
- const tokens = tokenize(it.text);
15362
- if (tokens.length === 0) return { id: it.id, score: 0 };
15363
- let overlap = 0;
15364
- const seen = /* @__PURE__ */ new Set();
15365
- for (const t of tokens) {
15366
- if (queryTokens.has(t) && !seen.has(t)) {
15367
- overlap++;
15368
- seen.add(t);
15369
- }
16277
+ // src/repositories/markdown.ts
16278
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync11 } from "fs";
16279
+ import { basename as basename5, dirname as dirname2, join as join15, resolve as resolve7 } from "path";
16280
+ import { stringify as stringifyYaml2 } from "yaml";
16281
+ function renderMarkdownFiles(pkg) {
16282
+ const bundleJson = JSON.stringify(pkg, null, 2) + "\n";
16283
+ const vitalDetails = renderVitalEvidence(pkg);
16284
+ const findings = renderFindings(pkg);
16285
+ const receipts = JSON.stringify({
16286
+ export_id: pkg.export_id,
16287
+ generated_at: pkg.generated_at,
16288
+ actions: pkg.actions
16289
+ }, null, 2) + "\n";
16290
+ const strategyFiles = pkg.strategies.map((entry) => ({
16291
+ relativePath: `strategies/${safeFilename(entry.strategy.slug || entry.strategy.title)}`,
16292
+ contents: renderStrategy(entry),
16293
+ description: `Strategy: ${entry.strategy.title}`
16294
+ }));
16295
+ return [
16296
+ {
16297
+ relativePath: "index.md",
16298
+ contents: renderIndex(pkg),
16299
+ description: "Repository export index"
16300
+ },
16301
+ {
16302
+ relativePath: "evidence/vital-signs.md",
16303
+ contents: vitalDetails,
16304
+ description: "Detailed vital-sign evidence"
16305
+ },
16306
+ {
16307
+ relativePath: "evidence/findings.md",
16308
+ contents: findings,
16309
+ description: "Findings and recommended plays"
16310
+ },
16311
+ ...strategyFiles,
16312
+ {
16313
+ relativePath: "receipts/actions.json",
16314
+ contents: receipts,
16315
+ description: "Action proposal and execution receipts"
16316
+ },
16317
+ {
16318
+ relativePath: "bundle.json",
16319
+ contents: bundleJson,
16320
+ description: "Canonical repository export package"
15370
16321
  }
15371
- return { id: it.id, score: overlap / Math.sqrt(tokens.length) };
15372
- });
15373
- const positive = scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score);
15374
- if (positive.length > 0) return positive.slice(0, topK);
15375
- return items.slice(-topK).reverse().map((it) => ({ id: it.id, score: 0 }));
16322
+ ];
15376
16323
  }
15377
- function cosine(a, b) {
15378
- let dot = 0;
15379
- let na = 0;
15380
- let nb = 0;
15381
- const len = Math.min(a.length, b.length);
15382
- for (let i = 0; i < len; i++) {
15383
- dot += a[i] * b[i];
15384
- na += a[i] * a[i];
15385
- nb += b[i] * b[i];
15386
- }
15387
- if (na === 0 || nb === 0) return 0;
15388
- return dot / (Math.sqrt(na) * Math.sqrt(nb));
16324
+ function renderIndex(pkg) {
16325
+ const frontmatter = stringifyYaml2({
16326
+ export_id: pkg.export_id,
16327
+ generated_at: pkg.generated_at,
16328
+ target: pkg.target.kind,
16329
+ overall_score: pkg.summary.overall_score,
16330
+ overall_status: pkg.summary.overall_status,
16331
+ gating_vital_sign: pkg.summary.gating_vital_sign,
16332
+ total_value_at_risk: pkg.summary.total_value_at_risk,
16333
+ tags: ["ntrp", "repository-export", pkg.summary.gating_vital_sign.replace(/_/g, "-")]
16334
+ }).trim();
16335
+ return [
16336
+ "---",
16337
+ frontmatter,
16338
+ "---",
16339
+ "",
16340
+ "# NTRP Repository Export",
16341
+ "",
16342
+ `Generated: ${pkg.generated_at}`,
16343
+ "",
16344
+ `Overall score: **${pkg.summary.overall_score}/100** (${pkg.summary.overall_status})`,
16345
+ `Gating vital sign: **${VITAL_SIGN_LABELS[pkg.summary.gating_vital_sign]}**`,
16346
+ `Total value at risk: **${pkg.summary.total_value_at_risk ? formatCurrency(pkg.summary.total_value_at_risk) : "N/A"}**`,
16347
+ "",
16348
+ "## Sections",
16349
+ "",
16350
+ ...pkg.sections.map(renderSection),
16351
+ "## Files",
16352
+ "",
16353
+ "- [[evidence/vital-signs|Vital-sign evidence]]",
16354
+ "- [[evidence/findings|Findings]]",
16355
+ "- `bundle.json`",
16356
+ "- `receipts/actions.json`",
16357
+ ""
16358
+ ].join("\n");
15389
16359
  }
15390
- async function rankByRelevance(query, items, topK) {
15391
- if (items.length === 0) return [];
15392
- try {
15393
- const { isEmbeddingsEnabled: isEmbeddingsEnabled2, embedText: embedText2, embedItems: embedItems2 } = await Promise.resolve().then(() => (init_embeddings(), embeddings_exports));
15394
- if (isEmbeddingsEnabled2()) {
15395
- const queryVec = await embedText2(query);
15396
- if (queryVec) {
15397
- const withVecs = await embedItems2(items);
15398
- 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);
15399
- if (scored.length > 0) return scored.slice(0, topK);
15400
- }
15401
- }
15402
- } catch {
15403
- }
15404
- return keywordRank(query, items, topK);
16360
+ function renderSection(section) {
16361
+ const childMarkdown = section.children && section.children.length > 0 ? ["", ...section.children.map(renderNestedSection)].join("\n") : "";
16362
+ return [
16363
+ "<details>",
16364
+ `<summary>${escapeSummary(section.title)}${section.summary ? ` \u2014 ${escapeSummary(section.summary)}` : ""}</summary>`,
16365
+ "",
16366
+ section.markdown,
16367
+ childMarkdown,
16368
+ "",
16369
+ "</details>",
16370
+ ""
16371
+ ].join("\n");
15405
16372
  }
15406
- var STOPWORDS;
15407
- var init_retrieval = __esm({
15408
- "src/memory/retrieval.ts"() {
15409
- "use strict";
15410
- STOPWORDS = /* @__PURE__ */ new Set([
15411
- "the",
15412
- "and",
15413
- "for",
15414
- "are",
15415
- "was",
15416
- "were",
15417
- "with",
15418
- "that",
15419
- "this",
15420
- "from",
15421
- "have",
15422
- "has",
15423
- "had",
15424
- "our",
15425
- "your",
15426
- "you",
15427
- "what",
15428
- "which",
15429
- "how",
15430
- "why",
15431
- "who",
15432
- "when",
15433
- "where",
15434
- "into",
15435
- "about",
15436
- "over",
15437
- "than",
15438
- "then",
15439
- "they",
15440
- "them",
15441
- "their",
15442
- "there",
15443
- "here",
15444
- "been",
15445
- "being",
15446
- "does",
15447
- "did",
15448
- "doing",
15449
- "can",
15450
- "could",
15451
- "would",
15452
- "should",
15453
- "will",
15454
- "shall",
15455
- "may",
15456
- "might",
15457
- "must",
15458
- "not",
15459
- "but",
15460
- "all",
15461
- "any",
15462
- "some",
15463
- "more",
15464
- "most",
15465
- "much",
15466
- "many",
15467
- "very",
15468
- "just",
15469
- "like",
15470
- "out",
15471
- "off",
15472
- "per",
15473
- "via",
15474
- "use",
15475
- "get",
15476
- "got"
15477
- ]);
16373
+ function renderNestedSection(section) {
16374
+ return [
16375
+ "<details>",
16376
+ `<summary>${escapeSummary(section.title)}${section.summary ? ` \u2014 ${escapeSummary(section.summary)}` : ""}</summary>`,
16377
+ "",
16378
+ section.markdown,
16379
+ "",
16380
+ "</details>"
16381
+ ].join("\n");
16382
+ }
16383
+ function renderVitalEvidence(pkg) {
16384
+ const lines = ["# Vital-Sign Evidence", ""];
16385
+ for (const vs of pkg.diagnosis.health.vital_signs) {
16386
+ lines.push(`## ${VITAL_SIGN_LABELS[vs.vital_sign]}`);
16387
+ lines.push("");
16388
+ lines.push(`Score: **${Math.round(vs.score)}/100** (${vs.status})`);
16389
+ lines.push(`Value: **${formatDollarImpact(vs.dollar_value, vs.dollar_label)}**`);
16390
+ lines.push(`Flagged entities: **${vs.entity_details.length}**`);
16391
+ lines.push("");
16392
+ lines.push("<details>");
16393
+ lines.push("<summary>Components</summary>");
16394
+ lines.push("");
16395
+ lines.push("```json");
16396
+ lines.push(JSON.stringify(vs.components, null, 2));
16397
+ lines.push("```");
16398
+ lines.push("");
16399
+ lines.push("</details>");
16400
+ lines.push("");
16401
+ lines.push("<details>");
16402
+ lines.push("<summary>Entity details</summary>");
16403
+ lines.push("");
16404
+ lines.push("```json");
16405
+ lines.push(JSON.stringify(vs.entity_details, null, 2));
16406
+ lines.push("```");
16407
+ lines.push("");
16408
+ lines.push("</details>");
16409
+ lines.push("");
15478
16410
  }
15479
- });
15480
-
15481
- // src/memory/knowledge.ts
15482
- import { existsSync as existsSync16, readFileSync as readFileSync14, appendFileSync as appendFileSync3, readdirSync as readdirSync3 } from "fs";
15483
- import { join as join17 } from "path";
15484
- import { randomUUID as randomUUID4 } from "crypto";
15485
- function knowledgePath() {
15486
- return join17(getMemoryDir(), KNOWLEDGE_FILE);
16411
+ return lines.join("\n");
15487
16412
  }
15488
- function loadKnowledgeChunks() {
15489
- const path = knowledgePath();
15490
- if (!existsSync16(path)) return [];
15491
- const out = [];
15492
- for (const line of readFileSync14(path, "utf-8").split("\n")) {
15493
- const trimmed = line.trim();
15494
- if (!trimmed) continue;
15495
- try {
15496
- out.push(JSON.parse(trimmed));
15497
- } catch {
16413
+ function renderFindings(pkg) {
16414
+ if (pkg.diagnosis.findings.length === 0) return "# Findings\n\n_No findings recorded._\n";
16415
+ const lines = ["# Findings", ""];
16416
+ for (const finding of pkg.diagnosis.findings) {
16417
+ lines.push(`## ${finding.severity.toUpperCase()} \u2014 ${finding.segment}`);
16418
+ lines.push("");
16419
+ lines.push(finding.finding);
16420
+ lines.push("");
16421
+ if (finding.dollar_value) lines.push(`Dollar value: **${formatCurrency(finding.dollar_value)}**`);
16422
+ if (finding.recommended_plays && finding.recommended_plays.length > 0) {
16423
+ lines.push(`Recommended plays: ${finding.recommended_plays.map((play) => `**${play.play_name}**`).join(", ")}`);
15498
16424
  }
16425
+ lines.push("");
15499
16426
  }
15500
- return out;
16427
+ return lines.join("\n");
15501
16428
  }
15502
- var KNOWLEDGE_FILE;
15503
- var init_knowledge = __esm({
15504
- "src/memory/knowledge.ts"() {
16429
+ function renderStrategy(entry) {
16430
+ const { strategy, sources } = entry;
16431
+ const frontmatter = stringifyYaml2({
16432
+ id: strategy.id,
16433
+ slug: strategy.slug,
16434
+ status: strategy.status,
16435
+ priority: strategy.priority,
16436
+ linked_play_ids: strategy.linked_play_ids,
16437
+ source_count: sources.length,
16438
+ updated_at: strategy.updated_at
16439
+ }).trim();
16440
+ return [
16441
+ "---",
16442
+ frontmatter,
16443
+ "---",
16444
+ "",
16445
+ `# ${strategy.title}`,
16446
+ "",
16447
+ `Goal: ${strategy.goal}`,
16448
+ "",
16449
+ `Hypothesis: ${strategy.hypothesis}`,
16450
+ "",
16451
+ `Target segment: ${strategy.target_segment}`,
16452
+ "",
16453
+ "## Recommended Actions",
16454
+ "",
16455
+ strategy.recommended_actions.length > 0 ? strategy.recommended_actions.map((action) => `- ${action}`).join("\n") : "_None specified._",
16456
+ "",
16457
+ "## Source Metadata",
16458
+ "",
16459
+ "```json",
16460
+ JSON.stringify(sources, null, 2),
16461
+ "```",
16462
+ ""
16463
+ ].join("\n");
16464
+ }
16465
+ function getRootPath(target) {
16466
+ return resolve7(target.directory ?? `ntrp-repository-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
16467
+ }
16468
+ function safeFilename(value) {
16469
+ return (basename5(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "strategy") + ".md";
16470
+ }
16471
+ function escapeSummary(value) {
16472
+ return value.replace(/[<>]/g, "");
16473
+ }
16474
+ var markdownRepositoryAdapter;
16475
+ var init_markdown2 = __esm({
16476
+ "src/repositories/markdown.ts"() {
15505
16477
  "use strict";
15506
- init_store();
15507
- init_readers();
15508
- KNOWLEDGE_FILE = "knowledge.jsonl";
16478
+ init_formatters();
16479
+ markdownRepositoryAdapter = {
16480
+ kind: "markdown",
16481
+ describeTarget(target) {
16482
+ return target.directory ? `local markdown folder ${resolve7(target.directory)}` : "local markdown folder";
16483
+ },
16484
+ planWrite(pkg) {
16485
+ const files = renderMarkdownFiles(pkg);
16486
+ return {
16487
+ target: pkg.target,
16488
+ root_path: getRootPath(pkg.target),
16489
+ files: files.map((file) => ({
16490
+ path: file.relativePath,
16491
+ bytes: Buffer.byteLength(file.contents, "utf-8"),
16492
+ description: file.description
16493
+ }))
16494
+ };
16495
+ },
16496
+ write(pkg) {
16497
+ const root = getRootPath(pkg.target);
16498
+ const files = renderMarkdownFiles(pkg);
16499
+ mkdirSync9(root, { recursive: true });
16500
+ const written = [];
16501
+ for (const file of files) {
16502
+ const absolutePath = join15(root, file.relativePath);
16503
+ mkdirSync9(dirname2(absolutePath), { recursive: true });
16504
+ writeFileSync11(absolutePath, file.contents, "utf-8");
16505
+ written.push(absolutePath);
16506
+ }
16507
+ return {
16508
+ mode: "repository_export",
16509
+ target: pkg.target,
16510
+ root_path: root,
16511
+ files_written: written,
16512
+ bundle_id: pkg.export_id,
16513
+ message: `Wrote ${written.length} repository export files to ${root}.`,
16514
+ external_side_effects: false
16515
+ };
16516
+ }
16517
+ };
15509
16518
  }
15510
16519
  });
15511
16520
 
15512
- // src/memory/store.ts
15513
- var store_exports2 = {};
15514
- __export(store_exports2, {
15515
- FACTS_JSONL: () => FACTS_JSONL,
15516
- LEDGER_JSONL: () => LEDGER_JSONL,
15517
- addFact: () => addFact,
15518
- buildMemoryBlock: () => buildMemoryBlock,
15519
- listFacts: () => listFacts,
15520
- listLedger: () => listLedger,
15521
- recordAnalysis: () => recordAnalysis,
15522
- rewriteJsonl: () => rewriteJsonl,
15523
- scrubText: () => scrubText
15524
- });
15525
- import { existsSync as existsSync17, readFileSync as readFileSync15, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync12 } from "fs";
15526
- import { join as join18 } from "path";
15527
- import { randomUUID as randomUUID5 } from "crypto";
15528
- function memPath(file) {
15529
- return join18(getMemoryDir(), file);
15530
- }
15531
- function readJsonl(file) {
15532
- const path = memPath(file);
15533
- if (!existsSync17(path)) return [];
15534
- const out = [];
15535
- for (const line of readFileSync15(path, "utf-8").split("\n")) {
15536
- const trimmed = line.trim();
15537
- if (!trimmed) continue;
15538
- try {
15539
- out.push(JSON.parse(trimmed));
15540
- } catch {
15541
- }
15542
- }
15543
- return out;
15544
- }
15545
- function appendJsonl(file, obj) {
15546
- try {
15547
- appendFileSync4(memPath(file), JSON.stringify(obj) + "\n");
15548
- } catch {
16521
+ // src/repositories/adapters.ts
16522
+ function getRepositoryAdapter(kind) {
16523
+ switch (kind) {
16524
+ case "markdown":
16525
+ return markdownRepositoryAdapter;
16526
+ case "notion":
16527
+ case "airtable":
16528
+ case "github":
16529
+ throw new NtrpError("repository_target_planned", `${kind} repository exports are planned but not implemented yet. Use --target markdown for now.`, 2 /* Usage */);
15549
16530
  }
15550
16531
  }
15551
- function rewriteJsonl(file, rows) {
15552
- try {
15553
- writeFileSync12(memPath(file), rows.map((r) => JSON.stringify(r)).join("\n") + (rows.length ? "\n" : ""));
15554
- } catch {
15555
- }
15556
- }
15557
- function scrubText(text) {
15558
- return text.replace(/[\w.+-]+@[\w-]+\.[\w.-]+/g, "[email]").trim();
15559
- }
15560
- function addFact(input) {
15561
- const fact = {
15562
- id: randomUUID5(),
15563
- text: scrubText(input.text),
15564
- kind: input.kind ?? "fact",
15565
- source: input.source ?? "user",
15566
- session_id: input.session_id,
15567
- created_at: (/* @__PURE__ */ new Date()).toISOString()
15568
- };
15569
- appendJsonl(FACTS_FILE, fact);
15570
- return fact;
15571
- }
15572
- function listFacts() {
15573
- return readJsonl(FACTS_FILE);
15574
- }
15575
- function summarizeAnswer(answer) {
15576
- const plain = answer.replace(/[#*`>_]/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\s+/g, " ").trim();
15577
- const match = plain.match(/^(.+?[.!?])(\s|$)/);
15578
- const sentence = match ? match[1] : plain;
15579
- return sentence.length > 200 ? sentence.slice(0, 200).replace(/\s+\S*$/, "") + "\u2026" : sentence;
15580
- }
15581
- function recordAnalysis(input) {
15582
- const entry = {
15583
- id: randomUUID5(),
15584
- question: scrubText(input.question).slice(0, 300),
15585
- summary: scrubText(summarizeAnswer(input.answer)),
15586
- tools: input.tools,
15587
- session_id: input.session_id,
15588
- created_at: (/* @__PURE__ */ new Date()).toISOString()
15589
- };
15590
- appendJsonl(LEDGER_FILE, entry);
15591
- return entry;
15592
- }
15593
- function listLedger() {
15594
- return readJsonl(LEDGER_FILE);
15595
- }
15596
- async function loadStrategySnippets() {
15597
- try {
15598
- const { getStrategies: getStrategies2 } = await Promise.resolve().then(() => (init_strategy(), strategy_exports));
15599
- const strategies = await getStrategies2("all");
15600
- return strategies.filter((s) => s.status === "active" || s.status === "draft").map((s) => ({
15601
- id: `strategy:${s.slug}`,
15602
- title: s.title,
15603
- text: `${s.title}. Goal: ${s.goal} Hypothesis: ${s.hypothesis} Target: ${s.target_segment}`
15604
- }));
15605
- } catch {
15606
- return [];
15607
- }
15608
- }
15609
- function loadWinSnippets() {
15610
- try {
15611
- const dir = getWinsDir();
15612
- const out = [];
15613
- for (const name of readdirSync4(dir)) {
15614
- if (!name.endsWith(".md") || name.toLowerCase() === "readme.md") continue;
15615
- const raw = readFileSync15(join18(dir, name), "utf-8");
15616
- const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? name.replace(/\.md$/, "");
15617
- const body = raw.replace(/^#.*$/m, "").replace(/\s+/g, " ").trim().slice(0, 300);
15618
- out.push({ id: `win:${name}`, title, text: `${title}. ${body}` });
15619
- }
15620
- return out;
15621
- } catch {
15622
- return [];
15623
- }
15624
- }
15625
- async function buildMemoryBlock(query, opts = {}) {
15626
- const maxFacts = opts.maxFacts ?? 8;
15627
- const maxLedger = opts.maxLedger ?? 6;
15628
- const maxStrategies = opts.maxStrategies ?? 4;
15629
- const maxWins = opts.maxWins ?? 3;
15630
- const maxKnowledge = opts.maxKnowledge ?? 4;
15631
- const facts = listFacts();
15632
- const ledger = listLedger();
15633
- const strategies = await loadStrategySnippets();
15634
- const wins = loadWinSnippets();
15635
- let knowledge = [];
15636
- try {
15637
- knowledge = loadKnowledgeChunks();
15638
- } catch {
15639
- knowledge = [];
15640
- }
15641
- const sections = [];
15642
- if (facts.length > 0) {
15643
- const ranked = await rankByRelevance(query, facts.map((f) => ({ id: f.id, text: f.text, embedding: f.embedding })), maxFacts);
15644
- const chosen = ranked.map((r) => facts.find((f) => f.id === r.id)).filter(Boolean);
15645
- if (chosen.length > 0) {
15646
- sections.push(
15647
- "Known facts & decisions:\n" + chosen.map((f) => `- ${f.text}${f.kind === "decision" ? " (decision)" : f.kind === "preference" ? " (preference)" : ""}`).join("\n")
15648
- );
15649
- }
15650
- }
15651
- if (strategies.length > 0) {
15652
- const ranked = await rankByRelevance(query, strategies, maxStrategies);
15653
- const chosen = ranked.map((r) => strategies.find((s) => s.id === r.id)).filter(Boolean);
15654
- if (chosen.length > 0) {
15655
- sections.push("Active strategies:\n" + chosen.map((s) => `- ${s.title}`).join("\n"));
15656
- }
15657
- }
15658
- if (wins.length > 0) {
15659
- const ranked = await rankByRelevance(query, wins, maxWins);
15660
- const chosen = ranked.map((r) => wins.find((w) => w.id === r.id)).filter(Boolean);
15661
- if (chosen.length > 0) {
15662
- sections.push("Logged wins (what has worked before):\n" + chosen.map((w) => `- ${w.title}`).join("\n"));
15663
- }
15664
- }
15665
- if (knowledge.length > 0) {
15666
- const ranked = await rankByRelevance(query, knowledge.map((k) => ({ id: k.id, text: k.text, embedding: k.embedding })), maxKnowledge);
15667
- const chosen = ranked.map((r) => knowledge.find((k) => k.id === r.id)).filter(Boolean);
15668
- if (chosen.length > 0) {
15669
- sections.push(
15670
- "Relevant external knowledge (case studies / frameworks you've ingested):\n" + chosen.map((k) => `- [${k.title}] ${k.text.replace(/\s+/g, " ").slice(0, 280)}`).join("\n")
15671
- );
15672
- }
15673
- }
15674
- if (ledger.length > 0) {
15675
- const ranked = await rankByRelevance(
15676
- query,
15677
- ledger.map((l) => ({ id: l.id, text: `${l.question} ${l.summary}`, embedding: l.embedding })),
15678
- maxLedger
15679
- );
15680
- const chosen = ranked.map((r) => ledger.find((l) => l.id === r.id)).filter(Boolean);
15681
- if (chosen.length > 0) {
15682
- sections.push(
15683
- "Analyses already run for this business (do NOT repeat these unless asked to revisit or connect them):\n" + chosen.map((l) => `- "${l.question}" \u2192 ${l.summary}`).join("\n")
15684
- );
15685
- }
15686
- }
15687
- return sections.join("\n\n");
15688
- }
15689
- var FACTS_FILE, LEDGER_FILE, FACTS_JSONL, LEDGER_JSONL;
15690
- var init_store2 = __esm({
15691
- "src/memory/store.ts"() {
15692
- "use strict";
15693
- init_store();
15694
- init_retrieval();
15695
- init_knowledge();
15696
- FACTS_FILE = "facts.jsonl";
15697
- LEDGER_FILE = "ledger.jsonl";
15698
- FACTS_JSONL = FACTS_FILE;
15699
- LEDGER_JSONL = LEDGER_FILE;
16532
+ var init_adapters = __esm({
16533
+ "src/repositories/adapters.ts"() {
16534
+ "use strict";
16535
+ init_markdown2();
16536
+ init_errors2();
16537
+ init_types2();
15700
16538
  }
15701
16539
  });
15702
16540
 
15703
- // src/mcp/server.ts
15704
- init_context2();
15705
- init_diagnosis();
15706
- init_metrics_analysis();
15707
- import { createInterface as createInterface2 } from "readline";
15708
-
15709
- // src/services/report.ts
15710
- init_schema();
15711
- init_queries();
15712
- init_errors2();
15713
- init_types2();
15714
- async function loadReportData(segmentName) {
16541
+ // src/services/publish.ts
16542
+ async function proposeRepositoryExport(options) {
15715
16543
  await initSchema();
15716
- const diagnosis = await loadLatestDiagnosis();
15717
- if (!diagnosis) {
15718
- throw new NtrpError("no_diagnosis", "No diagnosis data found. Run diagnose first.", 4 /* NoData */);
15719
- }
15720
- let { health, segments, findings, entityCounts } = diagnosis;
15721
- let scopedSegment = null;
15722
- if (segmentName) {
15723
- const lower = segmentName.toLowerCase();
15724
- let match = segments.find((s) => s.segment.name.toLowerCase() === lower);
15725
- if (!match) {
15726
- const subs = segments.filter((s) => s.segment.name.toLowerCase().includes(lower));
15727
- if (subs.length === 1) match = subs[0];
15728
- else if (subs.length > 1) {
15729
- throw new NtrpError("ambiguous_segment", `"${segmentName}" matches multiple segments.`, 2 /* Usage */, {
15730
- matches: subs.map((s) => s.segment.name)
15731
- });
15732
- }
15733
- }
15734
- if (!match) {
15735
- throw new NtrpError("segment_not_found", `No segment matching "${segmentName}".`, 2 /* Usage */, {
15736
- available_segments: segments.map((s) => s.segment.name)
15737
- });
15738
- }
15739
- scopedSegment = match;
15740
- health = match.result;
15741
- segments = [match];
15742
- findings = findings.filter((f) => f.segment.toLowerCase().includes(lower));
16544
+ const pkg = await buildPackage(options, "publish propose");
16545
+ const adapter = getRepositoryAdapter(pkg.target.kind);
16546
+ const plan = adapter.planWrite(pkg);
16547
+ const id = await insertActionProposal({
16548
+ handle_title: options.source === "smoke_protocol" ? "Monkey Pelican Export" : "NTRP Repository Export",
16549
+ kind: "repository_export",
16550
+ title: options.source === "smoke_protocol" ? "Monkey Pelican Export" : `Publish NTRP evidence bundle to ${pkg.target.kind}`,
16551
+ summary: `Export diagnosis, findings, strategies, evidence, and action receipts to ${adapter.describeTarget(pkg.target)}.`,
16552
+ permission_class: "execute",
16553
+ status: "pending_approval",
16554
+ target: {
16555
+ connector_id: `${pkg.target.kind}-repository`,
16556
+ connector_type: pkg.target.kind,
16557
+ operation: "write_repository_export"
16558
+ },
16559
+ payload: {
16560
+ repository_export: pkg,
16561
+ write_plan: plan
16562
+ },
16563
+ dry_run: {
16564
+ mode: "dry_run",
16565
+ summary: `Would write ${plan.files.length} file${plan.files.length === 1 ? "" : "s"} to ${plan.root_path}.`,
16566
+ would_execute: false,
16567
+ expected_mutations: plan.files.map((file) => `${file.path} (${file.description})`),
16568
+ risk_notes: [
16569
+ "Requires explicit local approval before writing files.",
16570
+ "Markdown target writes only to the local filesystem.",
16571
+ "Notion and Airtable targets are planned adapter mappings only in this slice."
16572
+ ]
16573
+ },
16574
+ source: options.source ?? "publish"
16575
+ });
16576
+ const proposal = await getActionProposal(id);
16577
+ if (!proposal) {
16578
+ throw new NtrpError("publish_proposal_missing", `Publish proposal was not found after insert: ${id}`, 1 /* RuntimeError */);
15743
16579
  }
15744
- return {
15745
- diagnosis,
15746
- scopedSegment,
15747
- reportData: {
15748
- health,
15749
- segments,
15750
- findings,
15751
- entityCounts,
15752
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
15753
- }
15754
- };
15755
- }
15756
-
15757
- // src/services/ask.ts
15758
- init_agentic_loop();
15759
-
15760
- // src/ai/explore-mode.ts
15761
- init_context2();
15762
- var DEEP_DIVE_PATTERNS = [
15763
- /\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,
15764
- /\b(how many|which deals|which accounts|who owns|top \d+|every deal|all stuck)\b/i
15765
- ];
15766
- function isDeepDiveQuestion(question) {
15767
- return DEEP_DIVE_PATTERNS.some((p) => p.test(question.trim()));
16580
+ return { action: "propose", proposal, plan };
15768
16581
  }
15769
- function resolveExploreResponseMode(question, ctx, priorTurnCount) {
15770
- if (isDeepDiveQuestion(question)) return "deep";
15771
- if (isAnalysisReady(ctx)) return "brief";
15772
- if (ctx.stage === "analyzed" || ctx.analysis.completed.length > 0) return "brief";
15773
- if (priorTurnCount === 0) return "deep";
15774
- return "brief";
16582
+ async function buildPackage(options, command) {
16583
+ const target = {
16584
+ kind: options.target,
16585
+ directory: options.directory
16586
+ };
16587
+ return buildRepositoryExportPackage({
16588
+ target,
16589
+ command,
16590
+ source: options.source ?? "publish",
16591
+ modelOrFixture: options.modelOrFixture
16592
+ });
15775
16593
  }
15776
-
15777
- // src/services/ask.ts
15778
- init_health_score();
15779
- init_divergence();
15780
- init_repl_api();
15781
- init_context2();
15782
- init_phase();
15783
- init_session_analysis();
16594
+ var init_publish = __esm({
16595
+ "src/services/publish.ts"() {
16596
+ "use strict";
16597
+ init_queries();
16598
+ init_schema();
16599
+ init_errors2();
16600
+ init_types2();
16601
+ init_bundle();
16602
+ init_adapters();
16603
+ }
16604
+ });
15784
16605
 
15785
16606
  // src/services/smoke-protocol.ts
15786
- init_queries();
15787
- init_diagnosis();
15788
- init_strategy();
15789
16607
  import { join as join16 } from "path";
15790
-
15791
- // src/services/publish.ts
15792
- init_queries();
15793
- init_schema();
15794
- init_errors2();
15795
- init_types2();
15796
-
15797
- // src/repositories/bundle.ts
15798
- init_queries();
15799
- init_session_analysis();
15800
- init_schema();
15801
- init_formatters();
15802
- init_errors2();
15803
- init_types2();
15804
- async function buildRepositoryExportPackage(options) {
15805
- await initSchema();
15806
- const bundle = await loadSessionAnalysisBundle();
15807
- const diagnosis = bundle.diagnosis;
15808
- if (!diagnosis) {
15809
- if (bundle.metrics) {
15810
- throw new NtrpError(
15811
- "diagnosis_required",
15812
- "Publish packages require a GTM health snapshot. Run /diagnose (companion) or /handoff report for metrics-only export.",
15813
- 4 /* NoData */
15814
- );
15815
- }
15816
- if (!hasAnyAnalysis(bundle)) {
15817
- throw new NtrpError("diagnosis_required", "No analysis data found. Run /new, /diagnose, or /metrics first.", 4 /* NoData */);
15818
- }
15819
- throw new NtrpError("diagnosis_required", "No diagnosis data found. Run /diagnose first.", 4 /* NoData */);
15820
- }
15821
- const strategies = await listStrategies("all");
15822
- const strategiesWithSources = await Promise.all(
15823
- strategies.map(async (strategy) => ({
15824
- strategy,
15825
- sources: await listStrategySources(strategy.id)
15826
- }))
15827
- );
15828
- const proposals = await listActionProposals(100);
15829
- const actions = await Promise.all(
15830
- proposals.map(async (proposal) => ({
15831
- proposal,
15832
- executions: await listActionExecutions(proposal.id)
15833
- }))
16608
+ function isSmokeProtocolTrigger(input) {
16609
+ return normalize(input).includes(SMOKE_TRIGGER_PHRASE);
16610
+ }
16611
+ async function runSmokeProtocol(_input, ctx) {
16612
+ const diagnosis = await runDiagnosis({ findings: false, segments: true });
16613
+ ctx.snapshot.computeResult = {
16614
+ aggregate: diagnosis.health,
16615
+ segments: diagnosis.segments
16616
+ };
16617
+ ctx.snapshot.divergences = diagnosis.divergences;
16618
+ const gatingVital = diagnosis.health.vital_signs.find((vs) => vs.vital_sign === diagnosis.health.gating_vital_sign) ?? diagnosis.health.vital_signs[0];
16619
+ const play = getPlaysForVitalSign(diagnosis.health.gating_vital_sign)[0];
16620
+ const finding = buildSmokeFinding(
16621
+ diagnosis.health.vital_signs,
16622
+ gatingVital,
16623
+ play?.id ?? "review-playbook",
16624
+ play?.name ?? "Review the playbook"
15834
16625
  );
15835
- const sections = buildSections({
15836
- diagnosis,
15837
- strategiesCount: strategiesWithSources.length,
15838
- actionsCount: actions.length
16626
+ await insertFinding({
16627
+ findings: [finding],
16628
+ model_used: "smoke-protocol-v1",
16629
+ raw_prompt: `Smoke trigger: ${SMOKE_TRIGGER_PHRASE}`
16630
+ });
16631
+ const strategyResult = await addStrategyText(renderSmokeStrategy(finding, play?.name ?? "Review the playbook"), {
16632
+ useAi: false,
16633
+ sourceMetadata: {
16634
+ connector_kind: "smoke_protocol",
16635
+ connector_name: "Monkey Pelican Trigger",
16636
+ sync_mode: "local_fixture",
16637
+ write_back: "approval_required_future",
16638
+ trigger_phrase: SMOKE_TRIGGER_PHRASE
16639
+ }
16640
+ });
16641
+ const proposalResult = await proposeRepositoryExport({
16642
+ target: "markdown",
16643
+ directory: join16(getExportsDir(), "repository-smoke"),
16644
+ source: "smoke_protocol",
16645
+ modelOrFixture: "smoke-protocol-v1"
16646
+ });
16647
+ const answer = renderSmokeAnswer({
16648
+ overallScore: diagnosis.health.overall_score,
16649
+ overallStatus: diagnosis.health.overall_status,
16650
+ gatingVital: diagnosis.health.gating_vital_sign,
16651
+ totalValueAtRisk: diagnosis.health.total_value_at_risk,
16652
+ finding,
16653
+ strategyTitle: strategyResult.strategy.title,
16654
+ strategyPath: strategyResult.library_path
15839
16655
  });
15840
16656
  return {
15841
- schema_version: "ntrp.repository_export.v1",
15842
- export_id: uuid(),
15843
- generated_at: (/* @__PURE__ */ new Date()).toISOString(),
15844
- target: options.target,
15845
- summary: {
16657
+ answer,
16658
+ finding,
16659
+ strategy: strategyResult.strategy,
16660
+ action_proposal: proposalResult.proposal,
16661
+ health: {
15846
16662
  overall_score: diagnosis.health.overall_score,
15847
16663
  overall_status: diagnosis.health.overall_status,
15848
16664
  gating_vital_sign: diagnosis.health.gating_vital_sign,
15849
- total_value_at_risk: diagnosis.health.total_value_at_risk ?? null,
15850
- findings_count: diagnosis.findings.length,
15851
- strategies_count: strategiesWithSources.length,
15852
- action_proposals_count: actions.length
15853
- },
15854
- diagnosis: {
15855
- health: diagnosis.health,
15856
- segments: diagnosis.segments.map((segment) => ({
15857
- segment: segment.segment,
15858
- result: segment.result
15859
- })),
15860
- findings: diagnosis.findings,
15861
- entity_counts: diagnosis.entityCounts,
15862
- upload_batch_id: diagnosis.uploadBatchId
15863
- },
15864
- strategies: strategiesWithSources,
15865
- actions,
15866
- sections,
15867
- provenance: {
15868
- command: options.command ?? "publish",
15869
- model_or_fixture: options.modelOrFixture,
15870
- source: options.source ?? "local_duckdb",
15871
- notes: [
15872
- "Generated from the latest persisted diagnosis.",
15873
- "Repository writes are approval-gated through local action proposals."
15874
- ]
16665
+ total_value_at_risk: diagnosis.health.total_value_at_risk ?? null
15875
16666
  }
15876
16667
  };
15877
16668
  }
15878
- function buildSections(input) {
15879
- const { diagnosis } = input;
15880
- const health = diagnosis.health;
15881
- return [
15882
- {
15883
- id: "cover",
15884
- title: "Cover Summary",
15885
- summary: `${health.overall_score}/100 ${health.overall_status}, gated by ${health.gating_vital_sign}`,
15886
- markdown: [
15887
- `Overall score: **${health.overall_score}/100** (${health.overall_status})`,
15888
- `Gating vital sign: **${VITAL_SIGN_LABELS[health.gating_vital_sign]}**`,
15889
- `Total value at risk: **${health.total_value_at_risk ? formatCurrency(health.total_value_at_risk) : "N/A"}**`,
15890
- `Findings: **${diagnosis.findings.length}**`,
15891
- `Strategies: **${input.strategiesCount}**`,
15892
- `Action proposals: **${input.actionsCount}**`
15893
- ].join("\n\n")
15894
- },
15895
- {
15896
- id: "vital-signs",
15897
- title: "Vital Signs",
15898
- summary: `${health.vital_signs.length} vital signs`,
15899
- markdown: health.vital_signs.map((vs) => `- **${VITAL_SIGN_LABELS[vs.vital_sign]}:** ${Math.round(vs.score)}/100 (${vs.status}) \u2014 ${formatDollarImpact(vs.dollar_value, vs.dollar_label)}`).join("\n"),
15900
- children: health.vital_signs.map((vs) => ({
15901
- id: `vital-${vs.vital_sign}`,
15902
- title: `${VITAL_SIGN_LABELS[vs.vital_sign]}: ${Math.round(vs.score)}/100`,
15903
- summary: formatDollarImpact(vs.dollar_value, vs.dollar_label),
15904
- markdown: [
15905
- `Status: **${vs.status}**`,
15906
- `Value: **${formatDollarImpact(vs.dollar_value, vs.dollar_label)}**`,
15907
- `Flagged entities: **${vs.entity_details.length}**`,
15908
- "",
15909
- "Components:",
15910
- "```json",
15911
- JSON.stringify(vs.components, null, 2),
15912
- "```",
15913
- "",
15914
- "Top entity details:",
15915
- "```json",
15916
- JSON.stringify(vs.entity_details.slice(0, 25), null, 2),
15917
- "```"
15918
- ].join("\n"),
15919
- metadata: { vital_sign: vs.vital_sign }
15920
- }))
15921
- },
15922
- {
15923
- id: "findings",
15924
- title: "Findings and Deep Analysis",
15925
- summary: `${diagnosis.findings.length} findings`,
15926
- markdown: diagnosis.findings.length > 0 ? diagnosis.findings.map((finding) => `- **${finding.severity.toUpperCase()}** ${finding.segment}: ${finding.finding}`).join("\n") : "_No findings recorded._",
15927
- children: diagnosis.findings.map((finding, index) => ({
15928
- id: `finding-${index + 1}`,
15929
- title: `${finding.severity.toUpperCase()} \u2014 ${finding.segment}`,
15930
- summary: finding.dollar_value ? formatCurrency(finding.dollar_value) : void 0,
15931
- markdown: [
15932
- finding.finding,
15933
- "",
15934
- finding.recommended_plays && finding.recommended_plays.length > 0 ? `Recommended plays: ${finding.recommended_plays.map((play) => `**${play.play_name}**`).join(", ")}` : "Recommended plays: _None recorded._",
15935
- "",
15936
- "Scores:",
15937
- "```json",
15938
- JSON.stringify(finding.vital_signs, null, 2),
15939
- "```"
15940
- ].join("\n")
15941
- }))
15942
- },
15943
- {
15944
- id: "segments",
15945
- title: "Segments",
15946
- summary: `${diagnosis.segments.length} segments`,
15947
- markdown: diagnosis.segments.length > 0 ? diagnosis.segments.map((segment) => `- **${segment.segment.name}:** ${Math.round(segment.result.overall_score)}/100 (${segment.result.overall_status}), gated by ${segment.result.gating_vital_sign}`).join("\n") : "_No segments recorded._"
15948
- }
15949
- ];
16669
+ function buildSmokeFinding(vitals, gatingVital, playId, playName) {
16670
+ const vitalSigns = Object.fromEntries(vitals.map((vs) => [vs.vital_sign, Math.round(vs.score)]));
16671
+ const value = gatingVital?.dollar_value ?? null;
16672
+ const valueLabel = gatingVital?.dollar_label ?? "value at risk";
16673
+ const score = Math.round(gatingVital?.score ?? 0);
16674
+ const vital = gatingVital?.vital_sign ?? "freshness";
16675
+ const formattedValue = value === null ? "N/A" : formatCurrency(value);
16676
+ return {
16677
+ severity: gatingVital?.status === "red" ? "critical" : gatingVital?.status === "yellow" ? "warning" : "info",
16678
+ segment: "All Pipeline",
16679
+ finding: `**${formattedValue} ${valueLabel}** is the smoke-test headline. The current gating vital sign is **${vital}** at **${score}/100**, so the placeholder deep analysis would recommend **${playName}** as the next play.`,
16680
+ vital_signs: vitalSigns,
16681
+ entity_count: gatingVital?.entity_details.length ?? 0,
16682
+ recommended_focus: vital,
16683
+ dollar_value: value,
16684
+ recommended_plays: [{
16685
+ play_id: playId,
16686
+ play_name: playName,
16687
+ rationale: "Selected from the current gating vital sign to exercise the diagnosis-to-play smoke workflow."
16688
+ }]
16689
+ };
15950
16690
  }
16691
+ function renderSmokeStrategy(finding, playName) {
16692
+ return `# Smoke Test: ${playName}
15951
16693
 
15952
- // src/repositories/markdown.ts
15953
- init_formatters();
15954
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync11 } from "fs";
15955
- import { basename as basename5, dirname as dirname2, join as join15, resolve as resolve7 } from "path";
15956
- import { stringify as stringifyYaml2 } from "yaml";
15957
- var markdownRepositoryAdapter = {
15958
- kind: "markdown",
15959
- describeTarget(target) {
15960
- return target.directory ? `local markdown folder ${resolve7(target.directory)}` : "local markdown folder";
15961
- },
15962
- planWrite(pkg) {
15963
- const files = renderMarkdownFiles(pkg);
15964
- return {
15965
- target: pkg.target,
15966
- root_path: getRootPath(pkg.target),
15967
- files: files.map((file) => ({
15968
- path: file.relativePath,
15969
- bytes: Buffer.byteLength(file.contents, "utf-8"),
15970
- description: file.description
15971
- }))
15972
- };
15973
- },
15974
- write(pkg) {
15975
- const root = getRootPath(pkg.target);
15976
- const files = renderMarkdownFiles(pkg);
15977
- mkdirSync9(root, { recursive: true });
15978
- const written = [];
15979
- for (const file of files) {
15980
- const absolutePath = join15(root, file.relativePath);
15981
- mkdirSync9(dirname2(absolutePath), { recursive: true });
15982
- writeFileSync11(absolutePath, file.contents, "utf-8");
15983
- written.push(absolutePath);
15984
- }
15985
- return {
15986
- mode: "repository_export",
15987
- target: pkg.target,
15988
- root_path: root,
15989
- files_written: written,
15990
- bundle_id: pkg.export_id,
15991
- message: `Wrote ${written.length} repository export files to ${root}.`,
15992
- external_side_effects: false
15993
- };
16694
+ Goal: Validate the flow from natural-language trigger to diagnosis, deep-analysis-style response, saved strategy, and approval-gated library write-back.
16695
+
16696
+ Target Segment: ${finding.segment}
16697
+
16698
+ Recommended play: ${playName}
16699
+
16700
+ Smoke finding: ${finding.finding.replace(/\*\*/g, "")}
16701
+ `;
16702
+ }
16703
+ function renderSmokeAnswer(input) {
16704
+ const totalValue = input.totalValueAtRisk === null ? "N/A" : formatCurrency(input.totalValueAtRisk);
16705
+ return `### Smoke Protocol Complete
16706
+
16707
+ I treated the monkey/pelican phrase as a local smoke trigger and ran the placeholder protocol without calling an AI model.
16708
+
16709
+ - Diagnosis: overall score **${Math.round(input.overallScore)}/100** (${input.overallStatus}), gated by **${input.gatingVital}**, with **${totalValue}** total value at risk.
16710
+ - Deep analysis fixture: ${input.finding.finding}
16711
+ - Saved play/strategy: **${input.strategyTitle}** at \`${input.strategyPath}\`.
16712
+ - Repository export prepared for an approval-gated Obsidian-compatible markdown bundle.
16713
+
16714
+ ---
16715
+ *Next: run \`/actions continue\` to approve the export, then \`/actions continue\` again to write it to the repository.*`;
16716
+ }
16717
+ function normalize(input) {
16718
+ return input.trim().toLowerCase().replace(/\s+/g, " ");
16719
+ }
16720
+ var SMOKE_TRIGGER_PHRASE;
16721
+ var init_smoke_protocol = __esm({
16722
+ "src/services/smoke-protocol.ts"() {
16723
+ "use strict";
16724
+ init_queries();
16725
+ init_diagnosis();
16726
+ init_strategy();
16727
+ init_publish();
16728
+ init_playbook();
16729
+ init_store();
16730
+ init_formatters();
16731
+ SMOKE_TRIGGER_PHRASE = "the monkey is green and riding a pelican";
15994
16732
  }
15995
- };
15996
- function renderMarkdownFiles(pkg) {
15997
- const bundleJson = JSON.stringify(pkg, null, 2) + "\n";
15998
- const vitalDetails = renderVitalEvidence(pkg);
15999
- const findings = renderFindings(pkg);
16000
- const receipts = JSON.stringify({
16001
- export_id: pkg.export_id,
16002
- generated_at: pkg.generated_at,
16003
- actions: pkg.actions
16004
- }, null, 2) + "\n";
16005
- const strategyFiles = pkg.strategies.map((entry) => ({
16006
- relativePath: `strategies/${safeFilename(entry.strategy.slug || entry.strategy.title)}`,
16007
- contents: renderStrategy(entry),
16008
- description: `Strategy: ${entry.strategy.title}`
16009
- }));
16010
- return [
16011
- {
16012
- relativePath: "index.md",
16013
- contents: renderIndex(pkg),
16014
- description: "Repository export index"
16015
- },
16016
- {
16017
- relativePath: "evidence/vital-signs.md",
16018
- contents: vitalDetails,
16019
- description: "Detailed vital-sign evidence"
16020
- },
16021
- {
16022
- relativePath: "evidence/findings.md",
16023
- contents: findings,
16024
- description: "Findings and recommended plays"
16025
- },
16026
- ...strategyFiles,
16027
- {
16028
- relativePath: "receipts/actions.json",
16029
- contents: receipts,
16030
- description: "Action proposal and execution receipts"
16031
- },
16032
- {
16033
- relativePath: "bundle.json",
16034
- contents: bundleJson,
16035
- description: "Canonical repository export package"
16733
+ });
16734
+
16735
+ // src/ai/embeddings.ts
16736
+ var embeddings_exports = {};
16737
+ __export(embeddings_exports, {
16738
+ embedItems: () => embedItems,
16739
+ embedText: () => embedText,
16740
+ isEmbeddingsEnabled: () => isEmbeddingsEnabled
16741
+ });
16742
+ function resolveConfig() {
16743
+ const explicit = getConfigValue("embeddings-provider");
16744
+ if (explicit === "none") return null;
16745
+ const voyageKey = process.env.VOYAGE_API_KEY ?? getConfigValue("voyage-api-key");
16746
+ const openaiKey = getOpenAiApiKey();
16747
+ const provider = explicit ?? (voyageKey ? "voyage" : openaiKey ? "openai" : "none");
16748
+ if (provider === "voyage" && voyageKey) {
16749
+ return { provider, apiKey: voyageKey, model: VOYAGE_MODEL, url: "https://api.voyageai.com/v1/embeddings" };
16750
+ }
16751
+ if (provider === "openai" && openaiKey) {
16752
+ return { provider, apiKey: openaiKey, model: OPENAI_MODEL, url: "https://api.openai.com/v1/embeddings" };
16753
+ }
16754
+ return null;
16755
+ }
16756
+ function isEmbeddingsEnabled() {
16757
+ return resolveConfig() !== null;
16758
+ }
16759
+ async function callProvider(texts) {
16760
+ const cfg = resolveConfig();
16761
+ if (!cfg || texts.length === 0) return null;
16762
+ try {
16763
+ const res = await fetch(cfg.url, {
16764
+ method: "POST",
16765
+ headers: {
16766
+ "Content-Type": "application/json",
16767
+ Authorization: `Bearer ${cfg.apiKey}`
16768
+ },
16769
+ body: JSON.stringify({ input: texts, model: cfg.model })
16770
+ });
16771
+ if (!res.ok) return null;
16772
+ const json = await res.json();
16773
+ if (!json.data) return null;
16774
+ return json.data.map((d) => d.embedding);
16775
+ } catch {
16776
+ return null;
16777
+ }
16778
+ }
16779
+ async function embedText(text) {
16780
+ const key = text.trim();
16781
+ if (!key) return null;
16782
+ const cached2 = cache.get(key);
16783
+ if (cached2) return cached2;
16784
+ const result = await callProvider([key]);
16785
+ const vec = result?.[0] ?? null;
16786
+ if (vec) cache.set(key, vec);
16787
+ return vec;
16788
+ }
16789
+ async function embedItems(items) {
16790
+ const needing = [];
16791
+ const out = items.map((it, index) => {
16792
+ if (it.embedding && it.embedding.length > 0) return { ...it };
16793
+ const cached2 = cache.get(it.text.trim());
16794
+ if (cached2) return { ...it, embedding: cached2 };
16795
+ needing.push({ index, text: it.text });
16796
+ return { ...it };
16797
+ });
16798
+ if (needing.length === 0) return out;
16799
+ const vectors = await callProvider(needing.map((n) => n.text));
16800
+ if (!vectors) return out;
16801
+ needing.forEach((n, i) => {
16802
+ const vec = vectors[i];
16803
+ if (vec) {
16804
+ out[n.index].embedding = vec;
16805
+ cache.set(n.text.trim(), vec);
16036
16806
  }
16037
- ];
16807
+ });
16808
+ return out;
16038
16809
  }
16039
- function renderIndex(pkg) {
16040
- const frontmatter = stringifyYaml2({
16041
- export_id: pkg.export_id,
16042
- generated_at: pkg.generated_at,
16043
- target: pkg.target.kind,
16044
- overall_score: pkg.summary.overall_score,
16045
- overall_status: pkg.summary.overall_status,
16046
- gating_vital_sign: pkg.summary.gating_vital_sign,
16047
- total_value_at_risk: pkg.summary.total_value_at_risk,
16048
- tags: ["ntrp", "repository-export", pkg.summary.gating_vital_sign.replace(/_/g, "-")]
16049
- }).trim();
16050
- return [
16051
- "---",
16052
- frontmatter,
16053
- "---",
16054
- "",
16055
- "# NTRP Repository Export",
16056
- "",
16057
- `Generated: ${pkg.generated_at}`,
16058
- "",
16059
- `Overall score: **${pkg.summary.overall_score}/100** (${pkg.summary.overall_status})`,
16060
- `Gating vital sign: **${VITAL_SIGN_LABELS[pkg.summary.gating_vital_sign]}**`,
16061
- `Total value at risk: **${pkg.summary.total_value_at_risk ? formatCurrency(pkg.summary.total_value_at_risk) : "N/A"}**`,
16062
- "",
16063
- "## Sections",
16064
- "",
16065
- ...pkg.sections.map(renderSection),
16066
- "## Files",
16067
- "",
16068
- "- [[evidence/vital-signs|Vital-sign evidence]]",
16069
- "- [[evidence/findings|Findings]]",
16070
- "- `bundle.json`",
16071
- "- `receipts/actions.json`",
16072
- ""
16073
- ].join("\n");
16810
+ var VOYAGE_MODEL, OPENAI_MODEL, cache;
16811
+ var init_embeddings = __esm({
16812
+ "src/ai/embeddings.ts"() {
16813
+ "use strict";
16814
+ init_store();
16815
+ init_llm_config();
16816
+ VOYAGE_MODEL = "voyage-3";
16817
+ OPENAI_MODEL = "text-embedding-3-small";
16818
+ cache = /* @__PURE__ */ new Map();
16819
+ }
16820
+ });
16821
+
16822
+ // src/memory/retrieval.ts
16823
+ function tokenize(text) {
16824
+ return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 3 && !STOPWORDS.has(t));
16074
16825
  }
16075
- function renderSection(section) {
16076
- const childMarkdown = section.children && section.children.length > 0 ? ["", ...section.children.map(renderNestedSection)].join("\n") : "";
16077
- return [
16078
- "<details>",
16079
- `<summary>${escapeSummary(section.title)}${section.summary ? ` \u2014 ${escapeSummary(section.summary)}` : ""}</summary>`,
16080
- "",
16081
- section.markdown,
16082
- childMarkdown,
16083
- "",
16084
- "</details>",
16085
- ""
16086
- ].join("\n");
16826
+ function keywordRank(query, items, topK) {
16827
+ const queryTokens = new Set(tokenize(query));
16828
+ if (queryTokens.size === 0) {
16829
+ return items.slice(-topK).reverse().map((it) => ({ id: it.id, score: 0 }));
16830
+ }
16831
+ const scored = items.map((it) => {
16832
+ const tokens = tokenize(it.text);
16833
+ if (tokens.length === 0) return { id: it.id, score: 0 };
16834
+ let overlap = 0;
16835
+ const seen = /* @__PURE__ */ new Set();
16836
+ for (const t of tokens) {
16837
+ if (queryTokens.has(t) && !seen.has(t)) {
16838
+ overlap++;
16839
+ seen.add(t);
16840
+ }
16841
+ }
16842
+ return { id: it.id, score: overlap / Math.sqrt(tokens.length) };
16843
+ });
16844
+ const positive = scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score);
16845
+ if (positive.length > 0) return positive.slice(0, topK);
16846
+ return items.slice(-topK).reverse().map((it) => ({ id: it.id, score: 0 }));
16087
16847
  }
16088
- function renderNestedSection(section) {
16089
- return [
16090
- "<details>",
16091
- `<summary>${escapeSummary(section.title)}${section.summary ? ` \u2014 ${escapeSummary(section.summary)}` : ""}</summary>`,
16092
- "",
16093
- section.markdown,
16094
- "",
16095
- "</details>"
16096
- ].join("\n");
16848
+ function cosine(a, b) {
16849
+ let dot = 0;
16850
+ let na = 0;
16851
+ let nb = 0;
16852
+ const len = Math.min(a.length, b.length);
16853
+ for (let i = 0; i < len; i++) {
16854
+ dot += a[i] * b[i];
16855
+ na += a[i] * a[i];
16856
+ nb += b[i] * b[i];
16857
+ }
16858
+ if (na === 0 || nb === 0) return 0;
16859
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
16097
16860
  }
16098
- function renderVitalEvidence(pkg) {
16099
- const lines = ["# Vital-Sign Evidence", ""];
16100
- for (const vs of pkg.diagnosis.health.vital_signs) {
16101
- lines.push(`## ${VITAL_SIGN_LABELS[vs.vital_sign]}`);
16102
- lines.push("");
16103
- lines.push(`Score: **${Math.round(vs.score)}/100** (${vs.status})`);
16104
- lines.push(`Value: **${formatDollarImpact(vs.dollar_value, vs.dollar_label)}**`);
16105
- lines.push(`Flagged entities: **${vs.entity_details.length}**`);
16106
- lines.push("");
16107
- lines.push("<details>");
16108
- lines.push("<summary>Components</summary>");
16109
- lines.push("");
16110
- lines.push("```json");
16111
- lines.push(JSON.stringify(vs.components, null, 2));
16112
- lines.push("```");
16113
- lines.push("");
16114
- lines.push("</details>");
16115
- lines.push("");
16116
- lines.push("<details>");
16117
- lines.push("<summary>Entity details</summary>");
16118
- lines.push("");
16119
- lines.push("```json");
16120
- lines.push(JSON.stringify(vs.entity_details, null, 2));
16121
- lines.push("```");
16122
- lines.push("");
16123
- lines.push("</details>");
16124
- lines.push("");
16861
+ async function rankByRelevance(query, items, topK) {
16862
+ if (items.length === 0) return [];
16863
+ try {
16864
+ const { isEmbeddingsEnabled: isEmbeddingsEnabled2, embedText: embedText2, embedItems: embedItems2 } = await Promise.resolve().then(() => (init_embeddings(), embeddings_exports));
16865
+ if (isEmbeddingsEnabled2()) {
16866
+ const queryVec = await embedText2(query);
16867
+ if (queryVec) {
16868
+ const withVecs = await embedItems2(items);
16869
+ 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);
16870
+ if (scored.length > 0) return scored.slice(0, topK);
16871
+ }
16872
+ }
16873
+ } catch {
16874
+ }
16875
+ return keywordRank(query, items, topK);
16876
+ }
16877
+ var STOPWORDS;
16878
+ var init_retrieval = __esm({
16879
+ "src/memory/retrieval.ts"() {
16880
+ "use strict";
16881
+ STOPWORDS = /* @__PURE__ */ new Set([
16882
+ "the",
16883
+ "and",
16884
+ "for",
16885
+ "are",
16886
+ "was",
16887
+ "were",
16888
+ "with",
16889
+ "that",
16890
+ "this",
16891
+ "from",
16892
+ "have",
16893
+ "has",
16894
+ "had",
16895
+ "our",
16896
+ "your",
16897
+ "you",
16898
+ "what",
16899
+ "which",
16900
+ "how",
16901
+ "why",
16902
+ "who",
16903
+ "when",
16904
+ "where",
16905
+ "into",
16906
+ "about",
16907
+ "over",
16908
+ "than",
16909
+ "then",
16910
+ "they",
16911
+ "them",
16912
+ "their",
16913
+ "there",
16914
+ "here",
16915
+ "been",
16916
+ "being",
16917
+ "does",
16918
+ "did",
16919
+ "doing",
16920
+ "can",
16921
+ "could",
16922
+ "would",
16923
+ "should",
16924
+ "will",
16925
+ "shall",
16926
+ "may",
16927
+ "might",
16928
+ "must",
16929
+ "not",
16930
+ "but",
16931
+ "all",
16932
+ "any",
16933
+ "some",
16934
+ "more",
16935
+ "most",
16936
+ "much",
16937
+ "many",
16938
+ "very",
16939
+ "just",
16940
+ "like",
16941
+ "out",
16942
+ "off",
16943
+ "per",
16944
+ "via",
16945
+ "use",
16946
+ "get",
16947
+ "got"
16948
+ ]);
16125
16949
  }
16126
- return lines.join("\n");
16950
+ });
16951
+
16952
+ // src/memory/knowledge.ts
16953
+ import { existsSync as existsSync16, readFileSync as readFileSync14, appendFileSync as appendFileSync3, readdirSync as readdirSync3 } from "fs";
16954
+ import { join as join17 } from "path";
16955
+ import { randomUUID as randomUUID4 } from "crypto";
16956
+ function knowledgePath() {
16957
+ return join17(getMemoryDir(), KNOWLEDGE_FILE);
16127
16958
  }
16128
- function renderFindings(pkg) {
16129
- if (pkg.diagnosis.findings.length === 0) return "# Findings\n\n_No findings recorded._\n";
16130
- const lines = ["# Findings", ""];
16131
- for (const finding of pkg.diagnosis.findings) {
16132
- lines.push(`## ${finding.severity.toUpperCase()} \u2014 ${finding.segment}`);
16133
- lines.push("");
16134
- lines.push(finding.finding);
16135
- lines.push("");
16136
- if (finding.dollar_value) lines.push(`Dollar value: **${formatCurrency(finding.dollar_value)}**`);
16137
- if (finding.recommended_plays && finding.recommended_plays.length > 0) {
16138
- lines.push(`Recommended plays: ${finding.recommended_plays.map((play) => `**${play.play_name}**`).join(", ")}`);
16959
+ function loadKnowledgeChunks() {
16960
+ const path = knowledgePath();
16961
+ if (!existsSync16(path)) return [];
16962
+ const out = [];
16963
+ for (const line of readFileSync14(path, "utf-8").split("\n")) {
16964
+ const trimmed = line.trim();
16965
+ if (!trimmed) continue;
16966
+ try {
16967
+ out.push(JSON.parse(trimmed));
16968
+ } catch {
16139
16969
  }
16140
- lines.push("");
16141
- }
16142
- return lines.join("\n");
16143
- }
16144
- function renderStrategy(entry) {
16145
- const { strategy, sources } = entry;
16146
- const frontmatter = stringifyYaml2({
16147
- id: strategy.id,
16148
- slug: strategy.slug,
16149
- status: strategy.status,
16150
- priority: strategy.priority,
16151
- linked_play_ids: strategy.linked_play_ids,
16152
- source_count: sources.length,
16153
- updated_at: strategy.updated_at
16154
- }).trim();
16155
- return [
16156
- "---",
16157
- frontmatter,
16158
- "---",
16159
- "",
16160
- `# ${strategy.title}`,
16161
- "",
16162
- `Goal: ${strategy.goal}`,
16163
- "",
16164
- `Hypothesis: ${strategy.hypothesis}`,
16165
- "",
16166
- `Target segment: ${strategy.target_segment}`,
16167
- "",
16168
- "## Recommended Actions",
16169
- "",
16170
- strategy.recommended_actions.length > 0 ? strategy.recommended_actions.map((action) => `- ${action}`).join("\n") : "_None specified._",
16171
- "",
16172
- "## Source Metadata",
16173
- "",
16174
- "```json",
16175
- JSON.stringify(sources, null, 2),
16176
- "```",
16177
- ""
16178
- ].join("\n");
16179
- }
16180
- function getRootPath(target) {
16181
- return resolve7(target.directory ?? `ntrp-repository-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
16182
- }
16183
- function safeFilename(value) {
16184
- return (basename5(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "strategy") + ".md";
16185
- }
16186
- function escapeSummary(value) {
16187
- return value.replace(/[<>]/g, "");
16188
- }
16189
-
16190
- // src/repositories/adapters.ts
16191
- init_errors2();
16192
- init_types2();
16193
- function getRepositoryAdapter(kind) {
16194
- switch (kind) {
16195
- case "markdown":
16196
- return markdownRepositoryAdapter;
16197
- case "notion":
16198
- case "airtable":
16199
- case "github":
16200
- throw new NtrpError("repository_target_planned", `${kind} repository exports are planned but not implemented yet. Use --target markdown for now.`, 2 /* Usage */);
16201
16970
  }
16971
+ return out;
16202
16972
  }
16203
-
16204
- // src/services/publish.ts
16205
- async function proposeRepositoryExport(options) {
16206
- await initSchema();
16207
- const pkg = await buildPackage(options, "publish propose");
16208
- const adapter = getRepositoryAdapter(pkg.target.kind);
16209
- const plan = adapter.planWrite(pkg);
16210
- const id = await insertActionProposal({
16211
- handle_title: options.source === "smoke_protocol" ? "Monkey Pelican Export" : "NTRP Repository Export",
16212
- kind: "repository_export",
16213
- title: options.source === "smoke_protocol" ? "Monkey Pelican Export" : `Publish NTRP evidence bundle to ${pkg.target.kind}`,
16214
- summary: `Export diagnosis, findings, strategies, evidence, and action receipts to ${adapter.describeTarget(pkg.target)}.`,
16215
- permission_class: "execute",
16216
- status: "pending_approval",
16217
- target: {
16218
- connector_id: `${pkg.target.kind}-repository`,
16219
- connector_type: pkg.target.kind,
16220
- operation: "write_repository_export"
16221
- },
16222
- payload: {
16223
- repository_export: pkg,
16224
- write_plan: plan
16225
- },
16226
- dry_run: {
16227
- mode: "dry_run",
16228
- summary: `Would write ${plan.files.length} file${plan.files.length === 1 ? "" : "s"} to ${plan.root_path}.`,
16229
- would_execute: false,
16230
- expected_mutations: plan.files.map((file) => `${file.path} (${file.description})`),
16231
- risk_notes: [
16232
- "Requires explicit local approval before writing files.",
16233
- "Markdown target writes only to the local filesystem.",
16234
- "Notion and Airtable targets are planned adapter mappings only in this slice."
16235
- ]
16236
- },
16237
- source: options.source ?? "publish"
16238
- });
16239
- const proposal = await getActionProposal(id);
16240
- if (!proposal) {
16241
- throw new NtrpError("publish_proposal_missing", `Publish proposal was not found after insert: ${id}`, 1 /* RuntimeError */);
16973
+ var KNOWLEDGE_FILE;
16974
+ var init_knowledge = __esm({
16975
+ "src/memory/knowledge.ts"() {
16976
+ "use strict";
16977
+ init_store();
16978
+ init_readers();
16979
+ KNOWLEDGE_FILE = "knowledge.jsonl";
16242
16980
  }
16243
- return { action: "propose", proposal, plan };
16244
- }
16245
- async function buildPackage(options, command) {
16246
- const target = {
16247
- kind: options.target,
16248
- directory: options.directory
16249
- };
16250
- return buildRepositoryExportPackage({
16251
- target,
16252
- command,
16253
- source: options.source ?? "publish",
16254
- modelOrFixture: options.modelOrFixture
16255
- });
16256
- }
16981
+ });
16257
16982
 
16258
- // src/services/smoke-protocol.ts
16259
- init_playbook();
16260
- init_store();
16261
- init_formatters();
16262
- var SMOKE_TRIGGER_PHRASE = "the monkey is green and riding a pelican";
16263
- function isSmokeProtocolTrigger(input) {
16264
- return normalize(input).includes(SMOKE_TRIGGER_PHRASE);
16983
+ // src/memory/store.ts
16984
+ var store_exports2 = {};
16985
+ __export(store_exports2, {
16986
+ FACTS_JSONL: () => FACTS_JSONL,
16987
+ LEDGER_JSONL: () => LEDGER_JSONL,
16988
+ addFact: () => addFact,
16989
+ buildMemoryBlock: () => buildMemoryBlock,
16990
+ listFacts: () => listFacts,
16991
+ listLedger: () => listLedger,
16992
+ recordAnalysis: () => recordAnalysis,
16993
+ rewriteJsonl: () => rewriteJsonl,
16994
+ scrubText: () => scrubText
16995
+ });
16996
+ import { existsSync as existsSync17, readFileSync as readFileSync15, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync12 } from "fs";
16997
+ import { join as join18 } from "path";
16998
+ import { randomUUID as randomUUID5 } from "crypto";
16999
+ function memPath(file) {
17000
+ return join18(getMemoryDir(), file);
16265
17001
  }
16266
- async function runSmokeProtocol(_input, ctx) {
16267
- const diagnosis = await runDiagnosis({ findings: false, segments: true });
16268
- ctx.snapshot.computeResult = {
16269
- aggregate: diagnosis.health,
16270
- segments: diagnosis.segments
16271
- };
16272
- ctx.snapshot.divergences = diagnosis.divergences;
16273
- const gatingVital = diagnosis.health.vital_signs.find((vs) => vs.vital_sign === diagnosis.health.gating_vital_sign) ?? diagnosis.health.vital_signs[0];
16274
- const play = getPlaysForVitalSign(diagnosis.health.gating_vital_sign)[0];
16275
- const finding = buildSmokeFinding(
16276
- diagnosis.health.vital_signs,
16277
- gatingVital,
16278
- play?.id ?? "review-playbook",
16279
- play?.name ?? "Review the playbook"
16280
- );
16281
- await insertFinding({
16282
- findings: [finding],
16283
- model_used: "smoke-protocol-v1",
16284
- raw_prompt: `Smoke trigger: ${SMOKE_TRIGGER_PHRASE}`
16285
- });
16286
- const strategyResult = await addStrategyText(renderSmokeStrategy(finding, play?.name ?? "Review the playbook"), {
16287
- useAi: false,
16288
- sourceMetadata: {
16289
- connector_kind: "smoke_protocol",
16290
- connector_name: "Monkey Pelican Trigger",
16291
- sync_mode: "local_fixture",
16292
- write_back: "approval_required_future",
16293
- trigger_phrase: SMOKE_TRIGGER_PHRASE
16294
- }
16295
- });
16296
- const proposalResult = await proposeRepositoryExport({
16297
- target: "markdown",
16298
- directory: join16(getExportsDir(), "repository-smoke"),
16299
- source: "smoke_protocol",
16300
- modelOrFixture: "smoke-protocol-v1"
16301
- });
16302
- const answer = renderSmokeAnswer({
16303
- overallScore: diagnosis.health.overall_score,
16304
- overallStatus: diagnosis.health.overall_status,
16305
- gatingVital: diagnosis.health.gating_vital_sign,
16306
- totalValueAtRisk: diagnosis.health.total_value_at_risk,
16307
- finding,
16308
- strategyTitle: strategyResult.strategy.title,
16309
- strategyPath: strategyResult.library_path
16310
- });
16311
- return {
16312
- answer,
16313
- finding,
16314
- strategy: strategyResult.strategy,
16315
- action_proposal: proposalResult.proposal,
16316
- health: {
16317
- overall_score: diagnosis.health.overall_score,
16318
- overall_status: diagnosis.health.overall_status,
16319
- gating_vital_sign: diagnosis.health.gating_vital_sign,
16320
- total_value_at_risk: diagnosis.health.total_value_at_risk ?? null
17002
+ function readJsonl(file) {
17003
+ const path = memPath(file);
17004
+ if (!existsSync17(path)) return [];
17005
+ const out = [];
17006
+ for (const line of readFileSync15(path, "utf-8").split("\n")) {
17007
+ const trimmed = line.trim();
17008
+ if (!trimmed) continue;
17009
+ try {
17010
+ out.push(JSON.parse(trimmed));
17011
+ } catch {
16321
17012
  }
17013
+ }
17014
+ return out;
17015
+ }
17016
+ function appendJsonl(file, obj) {
17017
+ try {
17018
+ appendFileSync4(memPath(file), JSON.stringify(obj) + "\n");
17019
+ } catch {
17020
+ }
17021
+ }
17022
+ function rewriteJsonl(file, rows) {
17023
+ try {
17024
+ writeFileSync12(memPath(file), rows.map((r) => JSON.stringify(r)).join("\n") + (rows.length ? "\n" : ""));
17025
+ } catch {
17026
+ }
17027
+ }
17028
+ function scrubText(text) {
17029
+ return text.replace(/[\w.+-]+@[\w-]+\.[\w.-]+/g, "[email]").trim();
17030
+ }
17031
+ function addFact(input) {
17032
+ const fact = {
17033
+ id: randomUUID5(),
17034
+ text: scrubText(input.text),
17035
+ kind: input.kind ?? "fact",
17036
+ source: input.source ?? "user",
17037
+ session_id: input.session_id,
17038
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
16322
17039
  };
17040
+ appendJsonl(FACTS_FILE, fact);
17041
+ return fact;
16323
17042
  }
16324
- function buildSmokeFinding(vitals, gatingVital, playId, playName) {
16325
- const vitalSigns = Object.fromEntries(vitals.map((vs) => [vs.vital_sign, Math.round(vs.score)]));
16326
- const value = gatingVital?.dollar_value ?? null;
16327
- const valueLabel = gatingVital?.dollar_label ?? "value at risk";
16328
- const score = Math.round(gatingVital?.score ?? 0);
16329
- const vital = gatingVital?.vital_sign ?? "freshness";
16330
- const formattedValue = value === null ? "N/A" : formatCurrency(value);
16331
- return {
16332
- severity: gatingVital?.status === "red" ? "critical" : gatingVital?.status === "yellow" ? "warning" : "info",
16333
- segment: "All Pipeline",
16334
- finding: `**${formattedValue} ${valueLabel}** is the smoke-test headline. The current gating vital sign is **${vital}** at **${score}/100**, so the placeholder deep analysis would recommend **${playName}** as the next play.`,
16335
- vital_signs: vitalSigns,
16336
- entity_count: gatingVital?.entity_details.length ?? 0,
16337
- recommended_focus: vital,
16338
- dollar_value: value,
16339
- recommended_plays: [{
16340
- play_id: playId,
16341
- play_name: playName,
16342
- rationale: "Selected from the current gating vital sign to exercise the diagnosis-to-play smoke workflow."
16343
- }]
17043
+ function listFacts() {
17044
+ return readJsonl(FACTS_FILE);
17045
+ }
17046
+ function summarizeAnswer(answer) {
17047
+ const plain = answer.replace(/[#*`>_]/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\s+/g, " ").trim();
17048
+ const match = plain.match(/^(.+?[.!?])(\s|$)/);
17049
+ const sentence = match ? match[1] : plain;
17050
+ return sentence.length > 200 ? sentence.slice(0, 200).replace(/\s+\S*$/, "") + "\u2026" : sentence;
17051
+ }
17052
+ function recordAnalysis(input) {
17053
+ const entry = {
17054
+ id: randomUUID5(),
17055
+ question: scrubText(input.question).slice(0, 300),
17056
+ summary: scrubText(summarizeAnswer(input.answer)),
17057
+ tools: input.tools,
17058
+ session_id: input.session_id,
17059
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
16344
17060
  };
17061
+ appendJsonl(LEDGER_FILE, entry);
17062
+ return entry;
16345
17063
  }
16346
- function renderSmokeStrategy(finding, playName) {
16347
- return `# Smoke Test: ${playName}
16348
-
16349
- Goal: Validate the flow from natural-language trigger to diagnosis, deep-analysis-style response, saved strategy, and approval-gated library write-back.
16350
-
16351
- Target Segment: ${finding.segment}
16352
-
16353
- Recommended play: ${playName}
16354
-
16355
- Smoke finding: ${finding.finding.replace(/\*\*/g, "")}
16356
- `;
17064
+ function listLedger() {
17065
+ return readJsonl(LEDGER_FILE);
16357
17066
  }
16358
- function renderSmokeAnswer(input) {
16359
- const totalValue = input.totalValueAtRisk === null ? "N/A" : formatCurrency(input.totalValueAtRisk);
16360
- return `### Smoke Protocol Complete
16361
-
16362
- I treated the monkey/pelican phrase as a local smoke trigger and ran the placeholder protocol without calling an AI model.
16363
-
16364
- - Diagnosis: overall score **${Math.round(input.overallScore)}/100** (${input.overallStatus}), gated by **${input.gatingVital}**, with **${totalValue}** total value at risk.
16365
- - Deep analysis fixture: ${input.finding.finding}
16366
- - Saved play/strategy: **${input.strategyTitle}** at \`${input.strategyPath}\`.
16367
- - Repository export prepared for an approval-gated Obsidian-compatible markdown bundle.
16368
-
16369
- ---
16370
- *Next: run \`/actions continue\` to approve the export, then \`/actions continue\` again to write it to the repository.*`;
17067
+ async function loadStrategySnippets() {
17068
+ try {
17069
+ const { getStrategies: getStrategies2 } = await Promise.resolve().then(() => (init_strategy(), strategy_exports));
17070
+ const strategies = await getStrategies2("all");
17071
+ return strategies.filter((s) => s.status === "active" || s.status === "draft").map((s) => ({
17072
+ id: `strategy:${s.slug}`,
17073
+ title: s.title,
17074
+ text: `${s.title}. Goal: ${s.goal} Hypothesis: ${s.hypothesis} Target: ${s.target_segment}`
17075
+ }));
17076
+ } catch {
17077
+ return [];
17078
+ }
16371
17079
  }
16372
- function normalize(input) {
16373
- return input.trim().toLowerCase().replace(/\s+/g, " ");
17080
+ function loadWinSnippets() {
17081
+ try {
17082
+ const dir = getWinsDir();
17083
+ const out = [];
17084
+ for (const name of readdirSync4(dir)) {
17085
+ if (!name.endsWith(".md") || name.toLowerCase() === "readme.md") continue;
17086
+ const raw = readFileSync15(join18(dir, name), "utf-8");
17087
+ const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? name.replace(/\.md$/, "");
17088
+ const body = raw.replace(/^#.*$/m, "").replace(/\s+/g, " ").trim().slice(0, 300);
17089
+ out.push({ id: `win:${name}`, title, text: `${title}. ${body}` });
17090
+ }
17091
+ return out;
17092
+ } catch {
17093
+ return [];
17094
+ }
17095
+ }
17096
+ async function buildMemoryBlock(query, opts = {}) {
17097
+ const maxFacts = opts.maxFacts ?? 8;
17098
+ const maxLedger = opts.maxLedger ?? 6;
17099
+ const maxStrategies = opts.maxStrategies ?? 4;
17100
+ const maxWins = opts.maxWins ?? 3;
17101
+ const maxKnowledge = opts.maxKnowledge ?? 4;
17102
+ const facts = listFacts();
17103
+ const ledger = listLedger();
17104
+ const strategies = await loadStrategySnippets();
17105
+ const wins = loadWinSnippets();
17106
+ let knowledge = [];
17107
+ try {
17108
+ knowledge = loadKnowledgeChunks();
17109
+ } catch {
17110
+ knowledge = [];
17111
+ }
17112
+ const sections = [];
17113
+ if (facts.length > 0) {
17114
+ const ranked = await rankByRelevance(query, facts.map((f) => ({ id: f.id, text: f.text, embedding: f.embedding })), maxFacts);
17115
+ const chosen = ranked.map((r) => facts.find((f) => f.id === r.id)).filter(Boolean);
17116
+ if (chosen.length > 0) {
17117
+ sections.push(
17118
+ "Known facts & decisions:\n" + chosen.map((f) => `- ${f.text}${f.kind === "decision" ? " (decision)" : f.kind === "preference" ? " (preference)" : ""}`).join("\n")
17119
+ );
17120
+ }
17121
+ }
17122
+ if (strategies.length > 0) {
17123
+ const ranked = await rankByRelevance(query, strategies, maxStrategies);
17124
+ const chosen = ranked.map((r) => strategies.find((s) => s.id === r.id)).filter(Boolean);
17125
+ if (chosen.length > 0) {
17126
+ sections.push("Active strategies:\n" + chosen.map((s) => `- ${s.title}`).join("\n"));
17127
+ }
17128
+ }
17129
+ if (wins.length > 0) {
17130
+ const ranked = await rankByRelevance(query, wins, maxWins);
17131
+ const chosen = ranked.map((r) => wins.find((w) => w.id === r.id)).filter(Boolean);
17132
+ if (chosen.length > 0) {
17133
+ sections.push("Logged wins (what has worked before):\n" + chosen.map((w) => `- ${w.title}`).join("\n"));
17134
+ }
17135
+ }
17136
+ if (knowledge.length > 0) {
17137
+ const ranked = await rankByRelevance(query, knowledge.map((k) => ({ id: k.id, text: k.text, embedding: k.embedding })), maxKnowledge);
17138
+ const chosen = ranked.map((r) => knowledge.find((k) => k.id === r.id)).filter(Boolean);
17139
+ if (chosen.length > 0) {
17140
+ sections.push(
17141
+ "Relevant external knowledge (case studies / frameworks you've ingested):\n" + chosen.map((k) => `- [${k.title}] ${k.text.replace(/\s+/g, " ").slice(0, 280)}`).join("\n")
17142
+ );
17143
+ }
17144
+ }
17145
+ if (ledger.length > 0) {
17146
+ const ranked = await rankByRelevance(
17147
+ query,
17148
+ ledger.map((l) => ({ id: l.id, text: `${l.question} ${l.summary}`, embedding: l.embedding })),
17149
+ maxLedger
17150
+ );
17151
+ const chosen = ranked.map((r) => ledger.find((l) => l.id === r.id)).filter(Boolean);
17152
+ if (chosen.length > 0) {
17153
+ sections.push(
17154
+ "Analyses already run for this business (do NOT repeat these unless asked to revisit or connect them):\n" + chosen.map((l) => `- "${l.question}" \u2192 ${l.summary}`).join("\n")
17155
+ );
17156
+ }
17157
+ }
17158
+ return sections.join("\n\n");
16374
17159
  }
17160
+ var FACTS_FILE, LEDGER_FILE, FACTS_JSONL, LEDGER_JSONL;
17161
+ var init_store2 = __esm({
17162
+ "src/memory/store.ts"() {
17163
+ "use strict";
17164
+ init_store();
17165
+ init_retrieval();
17166
+ init_knowledge();
17167
+ FACTS_FILE = "facts.jsonl";
17168
+ LEDGER_FILE = "ledger.jsonl";
17169
+ FACTS_JSONL = FACTS_FILE;
17170
+ LEDGER_JSONL = LEDGER_FILE;
17171
+ }
17172
+ });
16375
17173
 
16376
17174
  // src/services/ask.ts
16377
17175
  async function ensureAskSnapshot(ctx) {
@@ -16456,24 +17254,22 @@ async function runAsk(question, ctx) {
16456
17254
  }
16457
17255
  return { answer, tool_calls: toolCalls, findings, model_used: modelUsed, provider_used: providerUsed, failover };
16458
17256
  }
16459
-
16460
- // src/services/setup.ts
16461
- init_repl_api();
16462
- init_providers();
16463
- init_llm_config();
16464
- init_store();
16465
- init_profile();
16466
- import { existsSync as existsSync18, mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync13 } from "fs";
16467
- import { join as join19 } from "path";
16468
-
16469
- // src/license/verify.ts
16470
- init_store();
16471
- import { createHmac } from "crypto";
17257
+ var init_ask = __esm({
17258
+ "src/services/ask.ts"() {
17259
+ "use strict";
17260
+ init_agentic_loop();
17261
+ init_explore_mode();
17262
+ init_health_score();
17263
+ init_divergence();
17264
+ init_repl_api();
17265
+ init_context2();
17266
+ init_phase();
17267
+ init_session_analysis();
17268
+ init_smoke_protocol();
17269
+ }
17270
+ });
16472
17271
 
16473
17272
  // src/license/trial-policy.ts
16474
- var TRIAL_FULL_DAYS = 11;
16475
- var TRIAL_GRACE_END_DAYS = 30;
16476
- var TRIAL_ACTIVE_NUDGE_FROM_DAY = 8;
16477
17273
  function evaluateTrial(activatedAt, now2 = /* @__PURE__ */ new Date()) {
16478
17274
  const daysSince = Math.floor((now2.getTime() - activatedAt.getTime()) / 864e5);
16479
17275
  if (daysSince >= TRIAL_GRACE_END_DAYS) {
@@ -16509,38 +17305,65 @@ function formatTrialActiveMessage(daysSince) {
16509
17305
  const dayWord = daysLeft === 1 ? "day" : "days";
16510
17306
  return `trial license (${daysLeft} ${dayWord} remaining)`;
16511
17307
  }
17308
+ var TRIAL_FULL_DAYS, TRIAL_GRACE_END_DAYS, TRIAL_ACTIVE_NUDGE_FROM_DAY;
17309
+ var init_trial_policy = __esm({
17310
+ "src/license/trial-policy.ts"() {
17311
+ "use strict";
17312
+ TRIAL_FULL_DAYS = 11;
17313
+ TRIAL_GRACE_END_DAYS = 30;
17314
+ TRIAL_ACTIVE_NUDGE_FROM_DAY = 8;
17315
+ }
17316
+ });
16512
17317
 
16513
17318
  // src/license/upgrade-whimsy.ts
16514
- var CUTOFF_NUDGES = [
16515
- "Okay \u2014 that's the line. /upgrade and you're back.",
16516
- "We're paused until you say yes. /upgrade.",
16517
- "Didn't want it to end like this. /upgrade if you want in again.",
16518
- "Time's up. /upgrade \u2014 takes a minute.",
16519
- "I'll be here. You just need to /upgrade first.",
16520
- "That's all I can do on the free side. /upgrade.",
16521
- "Door's closed for now. /upgrade opens it."
16522
- ];
16523
17319
  function pick(items) {
16524
17320
  return items[Math.floor(Math.random() * items.length)] ?? items[0];
16525
17321
  }
16526
17322
  function randomCutoffNudge() {
16527
17323
  return pick(CUTOFF_NUDGES);
16528
17324
  }
17325
+ var CUTOFF_NUDGES;
17326
+ var init_upgrade_whimsy = __esm({
17327
+ "src/license/upgrade-whimsy.ts"() {
17328
+ "use strict";
17329
+ init_trial_policy();
17330
+ CUTOFF_NUDGES = [
17331
+ "Okay \u2014 that's the line. /upgrade and you're back.",
17332
+ "We're paused until you say yes. /upgrade.",
17333
+ "Didn't want it to end like this. /upgrade if you want in again.",
17334
+ "Time's up. /upgrade \u2014 takes a minute.",
17335
+ "I'll be here. You just need to /upgrade first.",
17336
+ "That's all I can do on the free side. /upgrade.",
17337
+ "Door's closed for now. /upgrade opens it."
17338
+ ];
17339
+ }
17340
+ });
16529
17341
 
16530
17342
  // src/license/normalize.ts
16531
- var NTRP_PREFIX = /^NTRP-/i;
16532
- var UUID_KEY = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
16533
17343
  function detectLicenseFormat(key) {
16534
17344
  if (NTRP_PREFIX.test(key)) return "ntrp";
16535
17345
  if (UUID_KEY.test(key)) return "lemonsqueezy";
16536
17346
  return "unknown";
16537
17347
  }
17348
+ var NTRP_PREFIX, UUID_KEY;
17349
+ var init_normalize = __esm({
17350
+ "src/license/normalize.ts"() {
17351
+ "use strict";
17352
+ NTRP_PREFIX = /^NTRP-/i;
17353
+ UUID_KEY = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
17354
+ }
17355
+ });
16538
17356
 
16539
17357
  // src/license/lemonsqueezy.ts
16540
17358
  import { hostname } from "os";
17359
+ var init_lemonsqueezy = __esm({
17360
+ "src/license/lemonsqueezy.ts"() {
17361
+ "use strict";
17362
+ }
17363
+ });
16541
17364
 
16542
17365
  // src/license/verify.ts
16543
- var SIGNING_SECRET = "ntrp-gtm-health-2026";
17366
+ import { createHmac } from "crypto";
16544
17367
  function validateLicenseKey(key) {
16545
17368
  const invalid = (msg) => ({
16546
17369
  valid: false,
@@ -16654,8 +17477,22 @@ function checkLicense() {
16654
17477
  }
16655
17478
  return applyTrialPolicy(result);
16656
17479
  }
17480
+ var SIGNING_SECRET;
17481
+ var init_verify = __esm({
17482
+ "src/license/verify.ts"() {
17483
+ "use strict";
17484
+ init_store();
17485
+ init_trial_policy();
17486
+ init_upgrade_whimsy();
17487
+ init_normalize();
17488
+ init_lemonsqueezy();
17489
+ SIGNING_SECRET = "ntrp-gtm-health-2026";
17490
+ }
17491
+ });
16657
17492
 
16658
17493
  // src/services/setup.ts
17494
+ import { existsSync as existsSync18, mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync13 } from "fs";
17495
+ import { join as join19 } from "path";
16659
17496
  function setupCheck() {
16660
17497
  const home = ntrpHome();
16661
17498
  let writable = false;
@@ -16699,18 +17536,22 @@ function setupCheck() {
16699
17536
  }
16700
17537
  };
16701
17538
  }
16702
-
16703
- // src/mcp/server.ts
16704
- init_playbook();
16705
- init_queries();
16706
- init_serialize();
16707
- init_errors2();
17539
+ var init_setup = __esm({
17540
+ "src/services/setup.ts"() {
17541
+ "use strict";
17542
+ init_repl_api();
17543
+ init_providers();
17544
+ init_llm_config();
17545
+ init_store();
17546
+ init_profile();
17547
+ init_verify();
17548
+ }
17549
+ });
16708
17550
 
16709
17551
  // src/version.ts
16710
17552
  import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
16711
17553
  import { dirname as dirname3, join as join20 } from "path";
16712
17554
  import { fileURLToPath } from "url";
16713
- var cachedVersion;
16714
17555
  function getInstalledVersion() {
16715
17556
  if (cachedVersion) return cachedVersion;
16716
17557
  const start = dirname3(fileURLToPath(import.meta.url));
@@ -16729,8 +17570,26 @@ function getInstalledVersion() {
16729
17570
  cachedVersion = "0.0.0";
16730
17571
  return cachedVersion;
16731
17572
  }
17573
+ var cachedVersion;
17574
+ var init_version = __esm({
17575
+ "src/version.ts"() {
17576
+ "use strict";
17577
+ }
17578
+ });
16732
17579
 
16733
17580
  // src/mcp/server.ts
17581
+ init_context2();
17582
+ init_diagnosis();
17583
+ init_metrics_analysis();
17584
+ init_report();
17585
+ init_ask();
17586
+ init_setup();
17587
+ init_playbook();
17588
+ init_queries();
17589
+ init_serialize();
17590
+ init_errors2();
17591
+ init_version();
17592
+ import { createInterface as createInterface2 } from "readline";
16734
17593
  var tools = [
16735
17594
  {
16736
17595
  name: "ntrp_setup_check",