@sonnechasser/ntrp 0.2.1 → 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.
- package/dist/index.js +45 -1
- package/dist/index.js.map +1 -1
- package/dist/investigation/verbosity-cli.js +1001 -195
- package/dist/investigation/verbosity-cli.js.map +1 -1
- package/dist/mcp/server.js +1987 -1131
- package/dist/mcp/server.js.map +1 -1
- package/dist/whimsy/time-bank-smoke.js +249 -196
- package/dist/whimsy/time-bank-smoke.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp/server.js
CHANGED
|
@@ -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
|
-
|
|
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%+.
|
|
@@ -14373,6 +15135,16 @@ FORMATTING (brief mode \u2014 scannable, not chunky):
|
|
|
14373
15135
|
- No ### headings, no tables, no --- footer, no closing "want me to dig deeper?" question.
|
|
14374
15136
|
- Single-point answers can stay one sentence \u2014 don't force bullets when one line is enough.
|
|
14375
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}`;
|
|
14376
15148
|
const outputRules = responseMode === "brief" ? `OUTPUT RULES:
|
|
14377
15149
|
- Respond in plain text markdown (not JSON). Lead sentence + bullets when helpful \u2014 never a dense paragraph wall.
|
|
14378
15150
|
- Pull numbers from the completed session analysis or conversation history \u2014 never guess.
|
|
@@ -14396,6 +15168,8 @@ ${VITAL_SIGNS_BLOCK}
|
|
|
14396
15168
|
|
|
14397
15169
|
PLAYBOOK \u2014 recommend these plays when appropriate:
|
|
14398
15170
|
${buildPlaybookBlock()}
|
|
15171
|
+
|
|
15172
|
+
${commandSection}
|
|
14399
15173
|
${formattingSection}
|
|
14400
15174
|
${outputRules}`;
|
|
14401
15175
|
}
|
|
@@ -14654,57 +15428,133 @@ var init_diagnosis = __esm({
|
|
|
14654
15428
|
}
|
|
14655
15429
|
});
|
|
14656
15430
|
|
|
14657
|
-
// src/
|
|
14658
|
-
function
|
|
14659
|
-
|
|
14660
|
-
const
|
|
14661
|
-
if (
|
|
14662
|
-
|
|
14663
|
-
}
|
|
14664
|
-
async function normalizeStrategy(input, ctx) {
|
|
14665
|
-
assertReplAi(ctx);
|
|
14666
|
-
const playbookBlock = getPlaybook().map((play) => `- ${play.id}: ${play.name} (${play.trigger_vital_sign}) \u2014 ${play.why}`).join("\n");
|
|
14667
|
-
const { text } = await llmCompleteText(
|
|
14668
|
-
"strategy",
|
|
14669
|
-
SYSTEM_PROMPT2,
|
|
14670
|
-
`AVAILABLE PLAYBOOK IDS:
|
|
14671
|
-
${playbookBlock}
|
|
14672
|
-
|
|
14673
|
-
SOURCE TYPE: ${input.source_type}
|
|
14674
|
-
SOURCE PATH: ${input.source_path ?? "none"}
|
|
14675
|
-
STRUCTURED HINT:
|
|
14676
|
-
${JSON.stringify(input.structured_hint ?? {}, null, 2)}
|
|
14677
|
-
|
|
14678
|
-
RAW STRATEGY DOCUMENT:
|
|
14679
|
-
${input.raw_text.slice(0, 16e3)}
|
|
14680
|
-
|
|
14681
|
-
Normalize this strategy now as strict JSON.`,
|
|
14682
|
-
1800,
|
|
14683
|
-
ctx
|
|
14684
|
-
);
|
|
14685
|
-
let parsed;
|
|
14686
|
-
try {
|
|
14687
|
-
parsed = JSON.parse(stripFences2(text));
|
|
14688
|
-
} catch {
|
|
14689
|
-
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 */);
|
|
14690
15437
|
}
|
|
14691
|
-
|
|
14692
|
-
|
|
14693
|
-
|
|
14694
|
-
|
|
14695
|
-
|
|
14696
|
-
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14706
|
-
|
|
14707
|
-
|
|
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"),
|
|
15557
|
+
linked_play_ids: validPlayIds(raw.linked_play_ids),
|
|
14708
15558
|
success_metrics: metricArray(raw.success_metrics),
|
|
14709
15559
|
leading_indicators: metricArray(raw.leading_indicators),
|
|
14710
15560
|
risks: stringArray(raw.risks),
|
|
@@ -15264,1117 +16114,1062 @@ var init_strategy = __esm({
|
|
|
15264
16114
|
}
|
|
15265
16115
|
});
|
|
15266
16116
|
|
|
15267
|
-
// src/
|
|
15268
|
-
|
|
15269
|
-
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
|
|
15274
|
-
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
return { provider, apiKey: openaiKey, model: OPENAI_MODEL, url: "https://api.openai.com/v1/embeddings" };
|
|
15285
|
-
}
|
|
15286
|
-
return null;
|
|
15287
|
-
}
|
|
15288
|
-
function isEmbeddingsEnabled() {
|
|
15289
|
-
return resolveConfig() !== null;
|
|
15290
|
-
}
|
|
15291
|
-
async function callProvider(texts) {
|
|
15292
|
-
const cfg = resolveConfig();
|
|
15293
|
-
if (!cfg || texts.length === 0) return null;
|
|
15294
|
-
try {
|
|
15295
|
-
const res = await fetch(cfg.url, {
|
|
15296
|
-
method: "POST",
|
|
15297
|
-
headers: {
|
|
15298
|
-
"Content-Type": "application/json",
|
|
15299
|
-
Authorization: `Bearer ${cfg.apiKey}`
|
|
15300
|
-
},
|
|
15301
|
-
body: JSON.stringify({ input: texts, model: cfg.model })
|
|
15302
|
-
});
|
|
15303
|
-
if (!res.ok) return null;
|
|
15304
|
-
const json = await res.json();
|
|
15305
|
-
if (!json.data) return null;
|
|
15306
|
-
return json.data.map((d) => d.embedding);
|
|
15307
|
-
} catch {
|
|
15308
|
-
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 */);
|
|
15309
16134
|
}
|
|
15310
|
-
|
|
15311
|
-
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
15316
|
-
|
|
15317
|
-
const
|
|
15318
|
-
|
|
15319
|
-
|
|
15320
|
-
|
|
15321
|
-
|
|
15322
|
-
|
|
15323
|
-
|
|
15324
|
-
|
|
15325
|
-
|
|
15326
|
-
|
|
15327
|
-
|
|
15328
|
-
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
|
|
15329
16153
|
});
|
|
15330
|
-
|
|
15331
|
-
|
|
15332
|
-
|
|
15333
|
-
|
|
15334
|
-
|
|
15335
|
-
|
|
15336
|
-
|
|
15337
|
-
|
|
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
|
+
]
|
|
15338
16189
|
}
|
|
15339
|
-
}
|
|
15340
|
-
return out;
|
|
16190
|
+
};
|
|
15341
16191
|
}
|
|
15342
|
-
|
|
15343
|
-
|
|
15344
|
-
|
|
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"() {
|
|
15345
16267
|
"use strict";
|
|
15346
|
-
|
|
15347
|
-
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
16268
|
+
init_queries();
|
|
16269
|
+
init_session_analysis();
|
|
16270
|
+
init_schema();
|
|
16271
|
+
init_formatters();
|
|
16272
|
+
init_errors2();
|
|
16273
|
+
init_types2();
|
|
15351
16274
|
}
|
|
15352
16275
|
});
|
|
15353
16276
|
|
|
15354
|
-
// src/
|
|
15355
|
-
|
|
15356
|
-
|
|
15357
|
-
}
|
|
15358
|
-
function
|
|
15359
|
-
const
|
|
15360
|
-
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
|
|
15364
|
-
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
|
|
15371
|
-
|
|
15372
|
-
|
|
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"
|
|
15373
16321
|
}
|
|
15374
|
-
|
|
15375
|
-
});
|
|
15376
|
-
const positive = scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score);
|
|
15377
|
-
if (positive.length > 0) return positive.slice(0, topK);
|
|
15378
|
-
return items.slice(-topK).reverse().map((it) => ({ id: it.id, score: 0 }));
|
|
16322
|
+
];
|
|
15379
16323
|
}
|
|
15380
|
-
function
|
|
15381
|
-
|
|
15382
|
-
|
|
15383
|
-
|
|
15384
|
-
|
|
15385
|
-
|
|
15386
|
-
|
|
15387
|
-
|
|
15388
|
-
|
|
15389
|
-
|
|
15390
|
-
|
|
15391
|
-
return
|
|
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");
|
|
15392
16359
|
}
|
|
15393
|
-
|
|
15394
|
-
|
|
15395
|
-
|
|
15396
|
-
|
|
15397
|
-
|
|
15398
|
-
|
|
15399
|
-
|
|
15400
|
-
|
|
15401
|
-
|
|
15402
|
-
|
|
15403
|
-
|
|
15404
|
-
|
|
15405
|
-
} catch {
|
|
15406
|
-
}
|
|
15407
|
-
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");
|
|
15408
16372
|
}
|
|
15409
|
-
|
|
15410
|
-
|
|
15411
|
-
|
|
15412
|
-
|
|
15413
|
-
|
|
15414
|
-
|
|
15415
|
-
|
|
15416
|
-
|
|
15417
|
-
|
|
15418
|
-
|
|
15419
|
-
|
|
15420
|
-
|
|
15421
|
-
|
|
15422
|
-
|
|
15423
|
-
|
|
15424
|
-
|
|
15425
|
-
|
|
15426
|
-
|
|
15427
|
-
|
|
15428
|
-
|
|
15429
|
-
|
|
15430
|
-
|
|
15431
|
-
|
|
15432
|
-
|
|
15433
|
-
|
|
15434
|
-
|
|
15435
|
-
|
|
15436
|
-
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
"here",
|
|
15447
|
-
"been",
|
|
15448
|
-
"being",
|
|
15449
|
-
"does",
|
|
15450
|
-
"did",
|
|
15451
|
-
"doing",
|
|
15452
|
-
"can",
|
|
15453
|
-
"could",
|
|
15454
|
-
"would",
|
|
15455
|
-
"should",
|
|
15456
|
-
"will",
|
|
15457
|
-
"shall",
|
|
15458
|
-
"may",
|
|
15459
|
-
"might",
|
|
15460
|
-
"must",
|
|
15461
|
-
"not",
|
|
15462
|
-
"but",
|
|
15463
|
-
"all",
|
|
15464
|
-
"any",
|
|
15465
|
-
"some",
|
|
15466
|
-
"more",
|
|
15467
|
-
"most",
|
|
15468
|
-
"much",
|
|
15469
|
-
"many",
|
|
15470
|
-
"very",
|
|
15471
|
-
"just",
|
|
15472
|
-
"like",
|
|
15473
|
-
"out",
|
|
15474
|
-
"off",
|
|
15475
|
-
"per",
|
|
15476
|
-
"via",
|
|
15477
|
-
"use",
|
|
15478
|
-
"get",
|
|
15479
|
-
"got"
|
|
15480
|
-
]);
|
|
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("");
|
|
15481
16410
|
}
|
|
15482
|
-
|
|
15483
|
-
|
|
15484
|
-
// src/memory/knowledge.ts
|
|
15485
|
-
import { existsSync as existsSync16, readFileSync as readFileSync14, appendFileSync as appendFileSync3, readdirSync as readdirSync3 } from "fs";
|
|
15486
|
-
import { join as join17 } from "path";
|
|
15487
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
15488
|
-
function knowledgePath() {
|
|
15489
|
-
return join17(getMemoryDir(), KNOWLEDGE_FILE);
|
|
16411
|
+
return lines.join("\n");
|
|
15490
16412
|
}
|
|
15491
|
-
function
|
|
15492
|
-
|
|
15493
|
-
|
|
15494
|
-
const
|
|
15495
|
-
|
|
15496
|
-
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
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(", ")}`);
|
|
15501
16424
|
}
|
|
16425
|
+
lines.push("");
|
|
15502
16426
|
}
|
|
15503
|
-
return
|
|
16427
|
+
return lines.join("\n");
|
|
15504
16428
|
}
|
|
15505
|
-
|
|
15506
|
-
|
|
15507
|
-
|
|
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"() {
|
|
15508
16477
|
"use strict";
|
|
15509
|
-
|
|
15510
|
-
|
|
15511
|
-
|
|
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
|
+
};
|
|
15512
16518
|
}
|
|
15513
16519
|
});
|
|
15514
16520
|
|
|
15515
|
-
// src/
|
|
15516
|
-
|
|
15517
|
-
|
|
15518
|
-
|
|
15519
|
-
|
|
15520
|
-
|
|
15521
|
-
|
|
15522
|
-
|
|
15523
|
-
|
|
15524
|
-
recordAnalysis: () => recordAnalysis,
|
|
15525
|
-
rewriteJsonl: () => rewriteJsonl,
|
|
15526
|
-
scrubText: () => scrubText
|
|
15527
|
-
});
|
|
15528
|
-
import { existsSync as existsSync17, readFileSync as readFileSync15, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync12 } from "fs";
|
|
15529
|
-
import { join as join18 } from "path";
|
|
15530
|
-
import { randomUUID as randomUUID5 } from "crypto";
|
|
15531
|
-
function memPath(file) {
|
|
15532
|
-
return join18(getMemoryDir(), file);
|
|
15533
|
-
}
|
|
15534
|
-
function readJsonl(file) {
|
|
15535
|
-
const path = memPath(file);
|
|
15536
|
-
if (!existsSync17(path)) return [];
|
|
15537
|
-
const out = [];
|
|
15538
|
-
for (const line of readFileSync15(path, "utf-8").split("\n")) {
|
|
15539
|
-
const trimmed = line.trim();
|
|
15540
|
-
if (!trimmed) continue;
|
|
15541
|
-
try {
|
|
15542
|
-
out.push(JSON.parse(trimmed));
|
|
15543
|
-
} catch {
|
|
15544
|
-
}
|
|
15545
|
-
}
|
|
15546
|
-
return out;
|
|
15547
|
-
}
|
|
15548
|
-
function appendJsonl(file, obj) {
|
|
15549
|
-
try {
|
|
15550
|
-
appendFileSync4(memPath(file), JSON.stringify(obj) + "\n");
|
|
15551
|
-
} 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 */);
|
|
15552
16530
|
}
|
|
15553
16531
|
}
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
|
|
15558
|
-
|
|
15559
|
-
|
|
15560
|
-
function scrubText(text) {
|
|
15561
|
-
return text.replace(/[\w.+-]+@[\w-]+\.[\w.-]+/g, "[email]").trim();
|
|
15562
|
-
}
|
|
15563
|
-
function addFact(input) {
|
|
15564
|
-
const fact = {
|
|
15565
|
-
id: randomUUID5(),
|
|
15566
|
-
text: scrubText(input.text),
|
|
15567
|
-
kind: input.kind ?? "fact",
|
|
15568
|
-
source: input.source ?? "user",
|
|
15569
|
-
session_id: input.session_id,
|
|
15570
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
15571
|
-
};
|
|
15572
|
-
appendJsonl(FACTS_FILE, fact);
|
|
15573
|
-
return fact;
|
|
15574
|
-
}
|
|
15575
|
-
function listFacts() {
|
|
15576
|
-
return readJsonl(FACTS_FILE);
|
|
15577
|
-
}
|
|
15578
|
-
function summarizeAnswer(answer) {
|
|
15579
|
-
const plain = answer.replace(/[#*`>_]/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\s+/g, " ").trim();
|
|
15580
|
-
const match = plain.match(/^(.+?[.!?])(\s|$)/);
|
|
15581
|
-
const sentence = match ? match[1] : plain;
|
|
15582
|
-
return sentence.length > 200 ? sentence.slice(0, 200).replace(/\s+\S*$/, "") + "\u2026" : sentence;
|
|
15583
|
-
}
|
|
15584
|
-
function recordAnalysis(input) {
|
|
15585
|
-
const entry = {
|
|
15586
|
-
id: randomUUID5(),
|
|
15587
|
-
question: scrubText(input.question).slice(0, 300),
|
|
15588
|
-
summary: scrubText(summarizeAnswer(input.answer)),
|
|
15589
|
-
tools: input.tools,
|
|
15590
|
-
session_id: input.session_id,
|
|
15591
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
15592
|
-
};
|
|
15593
|
-
appendJsonl(LEDGER_FILE, entry);
|
|
15594
|
-
return entry;
|
|
15595
|
-
}
|
|
15596
|
-
function listLedger() {
|
|
15597
|
-
return readJsonl(LEDGER_FILE);
|
|
15598
|
-
}
|
|
15599
|
-
async function loadStrategySnippets() {
|
|
15600
|
-
try {
|
|
15601
|
-
const { getStrategies: getStrategies2 } = await Promise.resolve().then(() => (init_strategy(), strategy_exports));
|
|
15602
|
-
const strategies = await getStrategies2("all");
|
|
15603
|
-
return strategies.filter((s) => s.status === "active" || s.status === "draft").map((s) => ({
|
|
15604
|
-
id: `strategy:${s.slug}`,
|
|
15605
|
-
title: s.title,
|
|
15606
|
-
text: `${s.title}. Goal: ${s.goal} Hypothesis: ${s.hypothesis} Target: ${s.target_segment}`
|
|
15607
|
-
}));
|
|
15608
|
-
} catch {
|
|
15609
|
-
return [];
|
|
15610
|
-
}
|
|
15611
|
-
}
|
|
15612
|
-
function loadWinSnippets() {
|
|
15613
|
-
try {
|
|
15614
|
-
const dir = getWinsDir();
|
|
15615
|
-
const out = [];
|
|
15616
|
-
for (const name of readdirSync4(dir)) {
|
|
15617
|
-
if (!name.endsWith(".md") || name.toLowerCase() === "readme.md") continue;
|
|
15618
|
-
const raw = readFileSync15(join18(dir, name), "utf-8");
|
|
15619
|
-
const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? name.replace(/\.md$/, "");
|
|
15620
|
-
const body = raw.replace(/^#.*$/m, "").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
15621
|
-
out.push({ id: `win:${name}`, title, text: `${title}. ${body}` });
|
|
15622
|
-
}
|
|
15623
|
-
return out;
|
|
15624
|
-
} catch {
|
|
15625
|
-
return [];
|
|
15626
|
-
}
|
|
15627
|
-
}
|
|
15628
|
-
async function buildMemoryBlock(query, opts = {}) {
|
|
15629
|
-
const maxFacts = opts.maxFacts ?? 8;
|
|
15630
|
-
const maxLedger = opts.maxLedger ?? 6;
|
|
15631
|
-
const maxStrategies = opts.maxStrategies ?? 4;
|
|
15632
|
-
const maxWins = opts.maxWins ?? 3;
|
|
15633
|
-
const maxKnowledge = opts.maxKnowledge ?? 4;
|
|
15634
|
-
const facts = listFacts();
|
|
15635
|
-
const ledger = listLedger();
|
|
15636
|
-
const strategies = await loadStrategySnippets();
|
|
15637
|
-
const wins = loadWinSnippets();
|
|
15638
|
-
let knowledge = [];
|
|
15639
|
-
try {
|
|
15640
|
-
knowledge = loadKnowledgeChunks();
|
|
15641
|
-
} catch {
|
|
15642
|
-
knowledge = [];
|
|
15643
|
-
}
|
|
15644
|
-
const sections = [];
|
|
15645
|
-
if (facts.length > 0) {
|
|
15646
|
-
const ranked = await rankByRelevance(query, facts.map((f) => ({ id: f.id, text: f.text, embedding: f.embedding })), maxFacts);
|
|
15647
|
-
const chosen = ranked.map((r) => facts.find((f) => f.id === r.id)).filter(Boolean);
|
|
15648
|
-
if (chosen.length > 0) {
|
|
15649
|
-
sections.push(
|
|
15650
|
-
"Known facts & decisions:\n" + chosen.map((f) => `- ${f.text}${f.kind === "decision" ? " (decision)" : f.kind === "preference" ? " (preference)" : ""}`).join("\n")
|
|
15651
|
-
);
|
|
15652
|
-
}
|
|
15653
|
-
}
|
|
15654
|
-
if (strategies.length > 0) {
|
|
15655
|
-
const ranked = await rankByRelevance(query, strategies, maxStrategies);
|
|
15656
|
-
const chosen = ranked.map((r) => strategies.find((s) => s.id === r.id)).filter(Boolean);
|
|
15657
|
-
if (chosen.length > 0) {
|
|
15658
|
-
sections.push("Active strategies:\n" + chosen.map((s) => `- ${s.title}`).join("\n"));
|
|
15659
|
-
}
|
|
15660
|
-
}
|
|
15661
|
-
if (wins.length > 0) {
|
|
15662
|
-
const ranked = await rankByRelevance(query, wins, maxWins);
|
|
15663
|
-
const chosen = ranked.map((r) => wins.find((w) => w.id === r.id)).filter(Boolean);
|
|
15664
|
-
if (chosen.length > 0) {
|
|
15665
|
-
sections.push("Logged wins (what has worked before):\n" + chosen.map((w) => `- ${w.title}`).join("\n"));
|
|
15666
|
-
}
|
|
15667
|
-
}
|
|
15668
|
-
if (knowledge.length > 0) {
|
|
15669
|
-
const ranked = await rankByRelevance(query, knowledge.map((k) => ({ id: k.id, text: k.text, embedding: k.embedding })), maxKnowledge);
|
|
15670
|
-
const chosen = ranked.map((r) => knowledge.find((k) => k.id === r.id)).filter(Boolean);
|
|
15671
|
-
if (chosen.length > 0) {
|
|
15672
|
-
sections.push(
|
|
15673
|
-
"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")
|
|
15674
|
-
);
|
|
15675
|
-
}
|
|
15676
|
-
}
|
|
15677
|
-
if (ledger.length > 0) {
|
|
15678
|
-
const ranked = await rankByRelevance(
|
|
15679
|
-
query,
|
|
15680
|
-
ledger.map((l) => ({ id: l.id, text: `${l.question} ${l.summary}`, embedding: l.embedding })),
|
|
15681
|
-
maxLedger
|
|
15682
|
-
);
|
|
15683
|
-
const chosen = ranked.map((r) => ledger.find((l) => l.id === r.id)).filter(Boolean);
|
|
15684
|
-
if (chosen.length > 0) {
|
|
15685
|
-
sections.push(
|
|
15686
|
-
"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")
|
|
15687
|
-
);
|
|
15688
|
-
}
|
|
15689
|
-
}
|
|
15690
|
-
return sections.join("\n\n");
|
|
15691
|
-
}
|
|
15692
|
-
var FACTS_FILE, LEDGER_FILE, FACTS_JSONL, LEDGER_JSONL;
|
|
15693
|
-
var init_store2 = __esm({
|
|
15694
|
-
"src/memory/store.ts"() {
|
|
15695
|
-
"use strict";
|
|
15696
|
-
init_store();
|
|
15697
|
-
init_retrieval();
|
|
15698
|
-
init_knowledge();
|
|
15699
|
-
FACTS_FILE = "facts.jsonl";
|
|
15700
|
-
LEDGER_FILE = "ledger.jsonl";
|
|
15701
|
-
FACTS_JSONL = FACTS_FILE;
|
|
15702
|
-
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();
|
|
15703
16538
|
}
|
|
15704
16539
|
});
|
|
15705
16540
|
|
|
15706
|
-
// src/
|
|
15707
|
-
|
|
15708
|
-
init_diagnosis();
|
|
15709
|
-
init_metrics_analysis();
|
|
15710
|
-
import { createInterface as createInterface2 } from "readline";
|
|
15711
|
-
|
|
15712
|
-
// src/services/report.ts
|
|
15713
|
-
init_schema();
|
|
15714
|
-
init_queries();
|
|
15715
|
-
init_errors2();
|
|
15716
|
-
init_types2();
|
|
15717
|
-
async function loadReportData(segmentName) {
|
|
16541
|
+
// src/services/publish.ts
|
|
16542
|
+
async function proposeRepositoryExport(options) {
|
|
15718
16543
|
await initSchema();
|
|
15719
|
-
const
|
|
15720
|
-
|
|
15721
|
-
|
|
15722
|
-
|
|
15723
|
-
|
|
15724
|
-
|
|
15725
|
-
|
|
15726
|
-
|
|
15727
|
-
|
|
15728
|
-
|
|
15729
|
-
|
|
15730
|
-
|
|
15731
|
-
|
|
15732
|
-
|
|
15733
|
-
|
|
15734
|
-
|
|
15735
|
-
|
|
15736
|
-
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
15740
|
-
}
|
|
15741
|
-
|
|
15742
|
-
|
|
15743
|
-
|
|
15744
|
-
|
|
15745
|
-
|
|
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 */);
|
|
15746
16579
|
}
|
|
15747
|
-
return {
|
|
15748
|
-
diagnosis,
|
|
15749
|
-
scopedSegment,
|
|
15750
|
-
reportData: {
|
|
15751
|
-
health,
|
|
15752
|
-
segments,
|
|
15753
|
-
findings,
|
|
15754
|
-
entityCounts,
|
|
15755
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15756
|
-
}
|
|
15757
|
-
};
|
|
15758
|
-
}
|
|
15759
|
-
|
|
15760
|
-
// src/services/ask.ts
|
|
15761
|
-
init_agentic_loop();
|
|
15762
|
-
|
|
15763
|
-
// src/ai/explore-mode.ts
|
|
15764
|
-
init_context2();
|
|
15765
|
-
var DEEP_DIVE_PATTERNS = [
|
|
15766
|
-
/\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,
|
|
15767
|
-
/\b(how many|which deals|which accounts|who owns|top \d+|every deal|all stuck)\b/i
|
|
15768
|
-
];
|
|
15769
|
-
function isDeepDiveQuestion(question) {
|
|
15770
|
-
return DEEP_DIVE_PATTERNS.some((p) => p.test(question.trim()));
|
|
16580
|
+
return { action: "propose", proposal, plan };
|
|
15771
16581
|
}
|
|
15772
|
-
function
|
|
15773
|
-
|
|
15774
|
-
|
|
15775
|
-
|
|
15776
|
-
|
|
15777
|
-
return
|
|
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
|
+
});
|
|
15778
16593
|
}
|
|
15779
|
-
|
|
15780
|
-
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
15786
|
-
|
|
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
|
+
});
|
|
15787
16605
|
|
|
15788
16606
|
// src/services/smoke-protocol.ts
|
|
15789
|
-
init_queries();
|
|
15790
|
-
init_diagnosis();
|
|
15791
|
-
init_strategy();
|
|
15792
16607
|
import { join as join16 } from "path";
|
|
15793
|
-
|
|
15794
|
-
|
|
15795
|
-
|
|
15796
|
-
|
|
15797
|
-
|
|
15798
|
-
|
|
15799
|
-
|
|
15800
|
-
|
|
15801
|
-
|
|
15802
|
-
|
|
15803
|
-
|
|
15804
|
-
|
|
15805
|
-
|
|
15806
|
-
|
|
15807
|
-
|
|
15808
|
-
|
|
15809
|
-
|
|
15810
|
-
const diagnosis = bundle.diagnosis;
|
|
15811
|
-
if (!diagnosis) {
|
|
15812
|
-
if (bundle.metrics) {
|
|
15813
|
-
throw new NtrpError(
|
|
15814
|
-
"diagnosis_required",
|
|
15815
|
-
"Publish packages require a GTM health snapshot. Run /diagnose (companion) or /handoff report for metrics-only export.",
|
|
15816
|
-
4 /* NoData */
|
|
15817
|
-
);
|
|
15818
|
-
}
|
|
15819
|
-
if (!hasAnyAnalysis(bundle)) {
|
|
15820
|
-
throw new NtrpError("diagnosis_required", "No analysis data found. Run /new, /diagnose, or /metrics first.", 4 /* NoData */);
|
|
15821
|
-
}
|
|
15822
|
-
throw new NtrpError("diagnosis_required", "No diagnosis data found. Run /diagnose first.", 4 /* NoData */);
|
|
15823
|
-
}
|
|
15824
|
-
const strategies = await listStrategies("all");
|
|
15825
|
-
const strategiesWithSources = await Promise.all(
|
|
15826
|
-
strategies.map(async (strategy) => ({
|
|
15827
|
-
strategy,
|
|
15828
|
-
sources: await listStrategySources(strategy.id)
|
|
15829
|
-
}))
|
|
15830
|
-
);
|
|
15831
|
-
const proposals = await listActionProposals(100);
|
|
15832
|
-
const actions = await Promise.all(
|
|
15833
|
-
proposals.map(async (proposal) => ({
|
|
15834
|
-
proposal,
|
|
15835
|
-
executions: await listActionExecutions(proposal.id)
|
|
15836
|
-
}))
|
|
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"
|
|
15837
16625
|
);
|
|
15838
|
-
|
|
15839
|
-
|
|
15840
|
-
|
|
15841
|
-
|
|
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
|
|
15842
16655
|
});
|
|
15843
16656
|
return {
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
16657
|
+
answer,
|
|
16658
|
+
finding,
|
|
16659
|
+
strategy: strategyResult.strategy,
|
|
16660
|
+
action_proposal: proposalResult.proposal,
|
|
16661
|
+
health: {
|
|
15849
16662
|
overall_score: diagnosis.health.overall_score,
|
|
15850
16663
|
overall_status: diagnosis.health.overall_status,
|
|
15851
16664
|
gating_vital_sign: diagnosis.health.gating_vital_sign,
|
|
15852
|
-
total_value_at_risk: diagnosis.health.total_value_at_risk ?? null
|
|
15853
|
-
findings_count: diagnosis.findings.length,
|
|
15854
|
-
strategies_count: strategiesWithSources.length,
|
|
15855
|
-
action_proposals_count: actions.length
|
|
15856
|
-
},
|
|
15857
|
-
diagnosis: {
|
|
15858
|
-
health: diagnosis.health,
|
|
15859
|
-
segments: diagnosis.segments.map((segment) => ({
|
|
15860
|
-
segment: segment.segment,
|
|
15861
|
-
result: segment.result
|
|
15862
|
-
})),
|
|
15863
|
-
findings: diagnosis.findings,
|
|
15864
|
-
entity_counts: diagnosis.entityCounts,
|
|
15865
|
-
upload_batch_id: diagnosis.uploadBatchId
|
|
15866
|
-
},
|
|
15867
|
-
strategies: strategiesWithSources,
|
|
15868
|
-
actions,
|
|
15869
|
-
sections,
|
|
15870
|
-
provenance: {
|
|
15871
|
-
command: options.command ?? "publish",
|
|
15872
|
-
model_or_fixture: options.modelOrFixture,
|
|
15873
|
-
source: options.source ?? "local_duckdb",
|
|
15874
|
-
notes: [
|
|
15875
|
-
"Generated from the latest persisted diagnosis.",
|
|
15876
|
-
"Repository writes are approval-gated through local action proposals."
|
|
15877
|
-
]
|
|
16665
|
+
total_value_at_risk: diagnosis.health.total_value_at_risk ?? null
|
|
15878
16666
|
}
|
|
15879
16667
|
};
|
|
15880
16668
|
}
|
|
15881
|
-
function
|
|
15882
|
-
const
|
|
15883
|
-
const
|
|
15884
|
-
|
|
15885
|
-
|
|
15886
|
-
|
|
15887
|
-
|
|
15888
|
-
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
|
|
15895
|
-
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
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"),
|
|
15903
|
-
children: health.vital_signs.map((vs) => ({
|
|
15904
|
-
id: `vital-${vs.vital_sign}`,
|
|
15905
|
-
title: `${VITAL_SIGN_LABELS[vs.vital_sign]}: ${Math.round(vs.score)}/100`,
|
|
15906
|
-
summary: formatDollarImpact(vs.dollar_value, vs.dollar_label),
|
|
15907
|
-
markdown: [
|
|
15908
|
-
`Status: **${vs.status}**`,
|
|
15909
|
-
`Value: **${formatDollarImpact(vs.dollar_value, vs.dollar_label)}**`,
|
|
15910
|
-
`Flagged entities: **${vs.entity_details.length}**`,
|
|
15911
|
-
"",
|
|
15912
|
-
"Components:",
|
|
15913
|
-
"```json",
|
|
15914
|
-
JSON.stringify(vs.components, null, 2),
|
|
15915
|
-
"```",
|
|
15916
|
-
"",
|
|
15917
|
-
"Top entity details:",
|
|
15918
|
-
"```json",
|
|
15919
|
-
JSON.stringify(vs.entity_details.slice(0, 25), null, 2),
|
|
15920
|
-
"```"
|
|
15921
|
-
].join("\n"),
|
|
15922
|
-
metadata: { vital_sign: vs.vital_sign }
|
|
15923
|
-
}))
|
|
15924
|
-
},
|
|
15925
|
-
{
|
|
15926
|
-
id: "findings",
|
|
15927
|
-
title: "Findings and Deep Analysis",
|
|
15928
|
-
summary: `${diagnosis.findings.length} findings`,
|
|
15929
|
-
markdown: diagnosis.findings.length > 0 ? diagnosis.findings.map((finding) => `- **${finding.severity.toUpperCase()}** ${finding.segment}: ${finding.finding}`).join("\n") : "_No findings recorded._",
|
|
15930
|
-
children: diagnosis.findings.map((finding, index) => ({
|
|
15931
|
-
id: `finding-${index + 1}`,
|
|
15932
|
-
title: `${finding.severity.toUpperCase()} \u2014 ${finding.segment}`,
|
|
15933
|
-
summary: finding.dollar_value ? formatCurrency(finding.dollar_value) : void 0,
|
|
15934
|
-
markdown: [
|
|
15935
|
-
finding.finding,
|
|
15936
|
-
"",
|
|
15937
|
-
finding.recommended_plays && finding.recommended_plays.length > 0 ? `Recommended plays: ${finding.recommended_plays.map((play) => `**${play.play_name}**`).join(", ")}` : "Recommended plays: _None recorded._",
|
|
15938
|
-
"",
|
|
15939
|
-
"Scores:",
|
|
15940
|
-
"```json",
|
|
15941
|
-
JSON.stringify(finding.vital_signs, null, 2),
|
|
15942
|
-
"```"
|
|
15943
|
-
].join("\n")
|
|
15944
|
-
}))
|
|
15945
|
-
},
|
|
15946
|
-
{
|
|
15947
|
-
id: "segments",
|
|
15948
|
-
title: "Segments",
|
|
15949
|
-
summary: `${diagnosis.segments.length} segments`,
|
|
15950
|
-
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._"
|
|
15951
|
-
}
|
|
15952
|
-
];
|
|
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
|
+
};
|
|
15953
16690
|
}
|
|
16691
|
+
function renderSmokeStrategy(finding, playName) {
|
|
16692
|
+
return `# Smoke Test: ${playName}
|
|
15954
16693
|
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
15965
|
-
|
|
15966
|
-
|
|
15967
|
-
|
|
15968
|
-
|
|
15969
|
-
|
|
15970
|
-
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
|
|
15974
|
-
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
|
|
15979
|
-
|
|
15980
|
-
|
|
15981
|
-
|
|
15982
|
-
|
|
15983
|
-
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
15993
|
-
bundle_id: pkg.export_id,
|
|
15994
|
-
message: `Wrote ${written.length} repository export files to ${root}.`,
|
|
15995
|
-
external_side_effects: false
|
|
15996
|
-
};
|
|
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";
|
|
15997
16732
|
}
|
|
15998
|
-
};
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
const
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
{
|
|
16015
|
-
|
|
16016
|
-
|
|
16017
|
-
|
|
16018
|
-
|
|
16019
|
-
|
|
16020
|
-
|
|
16021
|
-
|
|
16022
|
-
|
|
16023
|
-
|
|
16024
|
-
|
|
16025
|
-
|
|
16026
|
-
|
|
16027
|
-
|
|
16028
|
-
|
|
16029
|
-
|
|
16030
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16036
|
-
|
|
16037
|
-
|
|
16038
|
-
|
|
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);
|
|
16039
16806
|
}
|
|
16040
|
-
|
|
16807
|
+
});
|
|
16808
|
+
return out;
|
|
16041
16809
|
}
|
|
16042
|
-
|
|
16043
|
-
|
|
16044
|
-
|
|
16045
|
-
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
|
|
16051
|
-
|
|
16052
|
-
|
|
16053
|
-
|
|
16054
|
-
|
|
16055
|
-
|
|
16056
|
-
|
|
16057
|
-
"",
|
|
16058
|
-
"# NTRP Repository Export",
|
|
16059
|
-
"",
|
|
16060
|
-
`Generated: ${pkg.generated_at}`,
|
|
16061
|
-
"",
|
|
16062
|
-
`Overall score: **${pkg.summary.overall_score}/100** (${pkg.summary.overall_status})`,
|
|
16063
|
-
`Gating vital sign: **${VITAL_SIGN_LABELS[pkg.summary.gating_vital_sign]}**`,
|
|
16064
|
-
`Total value at risk: **${pkg.summary.total_value_at_risk ? formatCurrency(pkg.summary.total_value_at_risk) : "N/A"}**`,
|
|
16065
|
-
"",
|
|
16066
|
-
"## Sections",
|
|
16067
|
-
"",
|
|
16068
|
-
...pkg.sections.map(renderSection),
|
|
16069
|
-
"## Files",
|
|
16070
|
-
"",
|
|
16071
|
-
"- [[evidence/vital-signs|Vital-sign evidence]]",
|
|
16072
|
-
"- [[evidence/findings|Findings]]",
|
|
16073
|
-
"- `bundle.json`",
|
|
16074
|
-
"- `receipts/actions.json`",
|
|
16075
|
-
""
|
|
16076
|
-
].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));
|
|
16077
16825
|
}
|
|
16078
|
-
function
|
|
16079
|
-
const
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
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 }));
|
|
16090
16847
|
}
|
|
16091
|
-
function
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
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));
|
|
16100
16860
|
}
|
|
16101
|
-
function
|
|
16102
|
-
|
|
16103
|
-
|
|
16104
|
-
|
|
16105
|
-
|
|
16106
|
-
|
|
16107
|
-
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
|
|
16118
|
-
|
|
16119
|
-
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
|
|
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
|
+
]);
|
|
16128
16949
|
}
|
|
16129
|
-
|
|
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);
|
|
16130
16958
|
}
|
|
16131
|
-
function
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16135
|
-
|
|
16136
|
-
|
|
16137
|
-
|
|
16138
|
-
|
|
16139
|
-
|
|
16140
|
-
|
|
16141
|
-
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 {
|
|
16142
16969
|
}
|
|
16143
|
-
lines.push("");
|
|
16144
|
-
}
|
|
16145
|
-
return lines.join("\n");
|
|
16146
|
-
}
|
|
16147
|
-
function renderStrategy(entry) {
|
|
16148
|
-
const { strategy, sources } = entry;
|
|
16149
|
-
const frontmatter = stringifyYaml2({
|
|
16150
|
-
id: strategy.id,
|
|
16151
|
-
slug: strategy.slug,
|
|
16152
|
-
status: strategy.status,
|
|
16153
|
-
priority: strategy.priority,
|
|
16154
|
-
linked_play_ids: strategy.linked_play_ids,
|
|
16155
|
-
source_count: sources.length,
|
|
16156
|
-
updated_at: strategy.updated_at
|
|
16157
|
-
}).trim();
|
|
16158
|
-
return [
|
|
16159
|
-
"---",
|
|
16160
|
-
frontmatter,
|
|
16161
|
-
"---",
|
|
16162
|
-
"",
|
|
16163
|
-
`# ${strategy.title}`,
|
|
16164
|
-
"",
|
|
16165
|
-
`Goal: ${strategy.goal}`,
|
|
16166
|
-
"",
|
|
16167
|
-
`Hypothesis: ${strategy.hypothesis}`,
|
|
16168
|
-
"",
|
|
16169
|
-
`Target segment: ${strategy.target_segment}`,
|
|
16170
|
-
"",
|
|
16171
|
-
"## Recommended Actions",
|
|
16172
|
-
"",
|
|
16173
|
-
strategy.recommended_actions.length > 0 ? strategy.recommended_actions.map((action) => `- ${action}`).join("\n") : "_None specified._",
|
|
16174
|
-
"",
|
|
16175
|
-
"## Source Metadata",
|
|
16176
|
-
"",
|
|
16177
|
-
"```json",
|
|
16178
|
-
JSON.stringify(sources, null, 2),
|
|
16179
|
-
"```",
|
|
16180
|
-
""
|
|
16181
|
-
].join("\n");
|
|
16182
|
-
}
|
|
16183
|
-
function getRootPath(target) {
|
|
16184
|
-
return resolve7(target.directory ?? `ntrp-repository-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
|
|
16185
|
-
}
|
|
16186
|
-
function safeFilename(value) {
|
|
16187
|
-
return (basename5(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "strategy") + ".md";
|
|
16188
|
-
}
|
|
16189
|
-
function escapeSummary(value) {
|
|
16190
|
-
return value.replace(/[<>]/g, "");
|
|
16191
|
-
}
|
|
16192
|
-
|
|
16193
|
-
// src/repositories/adapters.ts
|
|
16194
|
-
init_errors2();
|
|
16195
|
-
init_types2();
|
|
16196
|
-
function getRepositoryAdapter(kind) {
|
|
16197
|
-
switch (kind) {
|
|
16198
|
-
case "markdown":
|
|
16199
|
-
return markdownRepositoryAdapter;
|
|
16200
|
-
case "notion":
|
|
16201
|
-
case "airtable":
|
|
16202
|
-
case "github":
|
|
16203
|
-
throw new NtrpError("repository_target_planned", `${kind} repository exports are planned but not implemented yet. Use --target markdown for now.`, 2 /* Usage */);
|
|
16204
16970
|
}
|
|
16971
|
+
return out;
|
|
16205
16972
|
}
|
|
16206
|
-
|
|
16207
|
-
|
|
16208
|
-
|
|
16209
|
-
|
|
16210
|
-
|
|
16211
|
-
|
|
16212
|
-
|
|
16213
|
-
const id = await insertActionProposal({
|
|
16214
|
-
handle_title: options.source === "smoke_protocol" ? "Monkey Pelican Export" : "NTRP Repository Export",
|
|
16215
|
-
kind: "repository_export",
|
|
16216
|
-
title: options.source === "smoke_protocol" ? "Monkey Pelican Export" : `Publish NTRP evidence bundle to ${pkg.target.kind}`,
|
|
16217
|
-
summary: `Export diagnosis, findings, strategies, evidence, and action receipts to ${adapter.describeTarget(pkg.target)}.`,
|
|
16218
|
-
permission_class: "execute",
|
|
16219
|
-
status: "pending_approval",
|
|
16220
|
-
target: {
|
|
16221
|
-
connector_id: `${pkg.target.kind}-repository`,
|
|
16222
|
-
connector_type: pkg.target.kind,
|
|
16223
|
-
operation: "write_repository_export"
|
|
16224
|
-
},
|
|
16225
|
-
payload: {
|
|
16226
|
-
repository_export: pkg,
|
|
16227
|
-
write_plan: plan
|
|
16228
|
-
},
|
|
16229
|
-
dry_run: {
|
|
16230
|
-
mode: "dry_run",
|
|
16231
|
-
summary: `Would write ${plan.files.length} file${plan.files.length === 1 ? "" : "s"} to ${plan.root_path}.`,
|
|
16232
|
-
would_execute: false,
|
|
16233
|
-
expected_mutations: plan.files.map((file) => `${file.path} (${file.description})`),
|
|
16234
|
-
risk_notes: [
|
|
16235
|
-
"Requires explicit local approval before writing files.",
|
|
16236
|
-
"Markdown target writes only to the local filesystem.",
|
|
16237
|
-
"Notion and Airtable targets are planned adapter mappings only in this slice."
|
|
16238
|
-
]
|
|
16239
|
-
},
|
|
16240
|
-
source: options.source ?? "publish"
|
|
16241
|
-
});
|
|
16242
|
-
const proposal = await getActionProposal(id);
|
|
16243
|
-
if (!proposal) {
|
|
16244
|
-
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";
|
|
16245
16980
|
}
|
|
16246
|
-
|
|
16247
|
-
}
|
|
16248
|
-
async function buildPackage(options, command) {
|
|
16249
|
-
const target = {
|
|
16250
|
-
kind: options.target,
|
|
16251
|
-
directory: options.directory
|
|
16252
|
-
};
|
|
16253
|
-
return buildRepositoryExportPackage({
|
|
16254
|
-
target,
|
|
16255
|
-
command,
|
|
16256
|
-
source: options.source ?? "publish",
|
|
16257
|
-
modelOrFixture: options.modelOrFixture
|
|
16258
|
-
});
|
|
16259
|
-
}
|
|
16981
|
+
});
|
|
16260
16982
|
|
|
16261
|
-
// src/
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
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);
|
|
16268
17001
|
}
|
|
16269
|
-
|
|
16270
|
-
const
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
|
|
16274
|
-
|
|
16275
|
-
|
|
16276
|
-
|
|
16277
|
-
|
|
16278
|
-
|
|
16279
|
-
diagnosis.health.vital_signs,
|
|
16280
|
-
gatingVital,
|
|
16281
|
-
play?.id ?? "review-playbook",
|
|
16282
|
-
play?.name ?? "Review the playbook"
|
|
16283
|
-
);
|
|
16284
|
-
await insertFinding({
|
|
16285
|
-
findings: [finding],
|
|
16286
|
-
model_used: "smoke-protocol-v1",
|
|
16287
|
-
raw_prompt: `Smoke trigger: ${SMOKE_TRIGGER_PHRASE}`
|
|
16288
|
-
});
|
|
16289
|
-
const strategyResult = await addStrategyText(renderSmokeStrategy(finding, play?.name ?? "Review the playbook"), {
|
|
16290
|
-
useAi: false,
|
|
16291
|
-
sourceMetadata: {
|
|
16292
|
-
connector_kind: "smoke_protocol",
|
|
16293
|
-
connector_name: "Monkey Pelican Trigger",
|
|
16294
|
-
sync_mode: "local_fixture",
|
|
16295
|
-
write_back: "approval_required_future",
|
|
16296
|
-
trigger_phrase: SMOKE_TRIGGER_PHRASE
|
|
16297
|
-
}
|
|
16298
|
-
});
|
|
16299
|
-
const proposalResult = await proposeRepositoryExport({
|
|
16300
|
-
target: "markdown",
|
|
16301
|
-
directory: join16(getExportsDir(), "repository-smoke"),
|
|
16302
|
-
source: "smoke_protocol",
|
|
16303
|
-
modelOrFixture: "smoke-protocol-v1"
|
|
16304
|
-
});
|
|
16305
|
-
const answer = renderSmokeAnswer({
|
|
16306
|
-
overallScore: diagnosis.health.overall_score,
|
|
16307
|
-
overallStatus: diagnosis.health.overall_status,
|
|
16308
|
-
gatingVital: diagnosis.health.gating_vital_sign,
|
|
16309
|
-
totalValueAtRisk: diagnosis.health.total_value_at_risk,
|
|
16310
|
-
finding,
|
|
16311
|
-
strategyTitle: strategyResult.strategy.title,
|
|
16312
|
-
strategyPath: strategyResult.library_path
|
|
16313
|
-
});
|
|
16314
|
-
return {
|
|
16315
|
-
answer,
|
|
16316
|
-
finding,
|
|
16317
|
-
strategy: strategyResult.strategy,
|
|
16318
|
-
action_proposal: proposalResult.proposal,
|
|
16319
|
-
health: {
|
|
16320
|
-
overall_score: diagnosis.health.overall_score,
|
|
16321
|
-
overall_status: diagnosis.health.overall_status,
|
|
16322
|
-
gating_vital_sign: diagnosis.health.gating_vital_sign,
|
|
16323
|
-
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 {
|
|
16324
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()
|
|
16325
17039
|
};
|
|
17040
|
+
appendJsonl(FACTS_FILE, fact);
|
|
17041
|
+
return fact;
|
|
16326
17042
|
}
|
|
16327
|
-
function
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
const
|
|
16332
|
-
const
|
|
16333
|
-
const
|
|
16334
|
-
return
|
|
16335
|
-
|
|
16336
|
-
|
|
16337
|
-
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
play_name: playName,
|
|
16345
|
-
rationale: "Selected from the current gating vital sign to exercise the diagnosis-to-play smoke workflow."
|
|
16346
|
-
}]
|
|
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()
|
|
16347
17060
|
};
|
|
17061
|
+
appendJsonl(LEDGER_FILE, entry);
|
|
17062
|
+
return entry;
|
|
16348
17063
|
}
|
|
16349
|
-
function
|
|
16350
|
-
return
|
|
16351
|
-
|
|
16352
|
-
Goal: Validate the flow from natural-language trigger to diagnosis, deep-analysis-style response, saved strategy, and approval-gated library write-back.
|
|
16353
|
-
|
|
16354
|
-
Target Segment: ${finding.segment}
|
|
16355
|
-
|
|
16356
|
-
Recommended play: ${playName}
|
|
16357
|
-
|
|
16358
|
-
Smoke finding: ${finding.finding.replace(/\*\*/g, "")}
|
|
16359
|
-
`;
|
|
17064
|
+
function listLedger() {
|
|
17065
|
+
return readJsonl(LEDGER_FILE);
|
|
16360
17066
|
}
|
|
16361
|
-
function
|
|
16362
|
-
|
|
16363
|
-
|
|
16364
|
-
|
|
16365
|
-
|
|
16366
|
-
|
|
16367
|
-
|
|
16368
|
-
|
|
16369
|
-
|
|
16370
|
-
|
|
16371
|
-
|
|
16372
|
-
|
|
16373
|
-
*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
|
+
}
|
|
16374
17079
|
}
|
|
16375
|
-
function
|
|
16376
|
-
|
|
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");
|
|
16377
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
|
+
});
|
|
16378
17173
|
|
|
16379
17174
|
// src/services/ask.ts
|
|
16380
17175
|
async function ensureAskSnapshot(ctx) {
|
|
@@ -16459,24 +17254,22 @@ async function runAsk(question, ctx) {
|
|
|
16459
17254
|
}
|
|
16460
17255
|
return { answer, tool_calls: toolCalls, findings, model_used: modelUsed, provider_used: providerUsed, failover };
|
|
16461
17256
|
}
|
|
16462
|
-
|
|
16463
|
-
|
|
16464
|
-
|
|
16465
|
-
|
|
16466
|
-
|
|
16467
|
-
|
|
16468
|
-
|
|
16469
|
-
|
|
16470
|
-
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
16474
|
-
|
|
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
|
+
});
|
|
16475
17271
|
|
|
16476
17272
|
// src/license/trial-policy.ts
|
|
16477
|
-
var TRIAL_FULL_DAYS = 11;
|
|
16478
|
-
var TRIAL_GRACE_END_DAYS = 30;
|
|
16479
|
-
var TRIAL_ACTIVE_NUDGE_FROM_DAY = 8;
|
|
16480
17273
|
function evaluateTrial(activatedAt, now2 = /* @__PURE__ */ new Date()) {
|
|
16481
17274
|
const daysSince = Math.floor((now2.getTime() - activatedAt.getTime()) / 864e5);
|
|
16482
17275
|
if (daysSince >= TRIAL_GRACE_END_DAYS) {
|
|
@@ -16512,38 +17305,65 @@ function formatTrialActiveMessage(daysSince) {
|
|
|
16512
17305
|
const dayWord = daysLeft === 1 ? "day" : "days";
|
|
16513
17306
|
return `trial license (${daysLeft} ${dayWord} remaining)`;
|
|
16514
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
|
+
});
|
|
16515
17317
|
|
|
16516
17318
|
// src/license/upgrade-whimsy.ts
|
|
16517
|
-
var CUTOFF_NUDGES = [
|
|
16518
|
-
"Okay \u2014 that's the line. /upgrade and you're back.",
|
|
16519
|
-
"We're paused until you say yes. /upgrade.",
|
|
16520
|
-
"Didn't want it to end like this. /upgrade if you want in again.",
|
|
16521
|
-
"Time's up. /upgrade \u2014 takes a minute.",
|
|
16522
|
-
"I'll be here. You just need to /upgrade first.",
|
|
16523
|
-
"That's all I can do on the free side. /upgrade.",
|
|
16524
|
-
"Door's closed for now. /upgrade opens it."
|
|
16525
|
-
];
|
|
16526
17319
|
function pick(items) {
|
|
16527
17320
|
return items[Math.floor(Math.random() * items.length)] ?? items[0];
|
|
16528
17321
|
}
|
|
16529
17322
|
function randomCutoffNudge() {
|
|
16530
17323
|
return pick(CUTOFF_NUDGES);
|
|
16531
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
|
+
});
|
|
16532
17341
|
|
|
16533
17342
|
// src/license/normalize.ts
|
|
16534
|
-
var NTRP_PREFIX = /^NTRP-/i;
|
|
16535
|
-
var UUID_KEY = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16536
17343
|
function detectLicenseFormat(key) {
|
|
16537
17344
|
if (NTRP_PREFIX.test(key)) return "ntrp";
|
|
16538
17345
|
if (UUID_KEY.test(key)) return "lemonsqueezy";
|
|
16539
17346
|
return "unknown";
|
|
16540
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
|
+
});
|
|
16541
17356
|
|
|
16542
17357
|
// src/license/lemonsqueezy.ts
|
|
16543
17358
|
import { hostname } from "os";
|
|
17359
|
+
var init_lemonsqueezy = __esm({
|
|
17360
|
+
"src/license/lemonsqueezy.ts"() {
|
|
17361
|
+
"use strict";
|
|
17362
|
+
}
|
|
17363
|
+
});
|
|
16544
17364
|
|
|
16545
17365
|
// src/license/verify.ts
|
|
16546
|
-
|
|
17366
|
+
import { createHmac } from "crypto";
|
|
16547
17367
|
function validateLicenseKey(key) {
|
|
16548
17368
|
const invalid = (msg) => ({
|
|
16549
17369
|
valid: false,
|
|
@@ -16657,8 +17477,22 @@ function checkLicense() {
|
|
|
16657
17477
|
}
|
|
16658
17478
|
return applyTrialPolicy(result);
|
|
16659
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
|
+
});
|
|
16660
17492
|
|
|
16661
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";
|
|
16662
17496
|
function setupCheck() {
|
|
16663
17497
|
const home = ntrpHome();
|
|
16664
17498
|
let writable = false;
|
|
@@ -16702,18 +17536,22 @@ function setupCheck() {
|
|
|
16702
17536
|
}
|
|
16703
17537
|
};
|
|
16704
17538
|
}
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
|
|
16710
|
-
|
|
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
|
+
});
|
|
16711
17550
|
|
|
16712
17551
|
// src/version.ts
|
|
16713
17552
|
import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
|
|
16714
17553
|
import { dirname as dirname3, join as join20 } from "path";
|
|
16715
17554
|
import { fileURLToPath } from "url";
|
|
16716
|
-
var cachedVersion;
|
|
16717
17555
|
function getInstalledVersion() {
|
|
16718
17556
|
if (cachedVersion) return cachedVersion;
|
|
16719
17557
|
const start = dirname3(fileURLToPath(import.meta.url));
|
|
@@ -16732,8 +17570,26 @@ function getInstalledVersion() {
|
|
|
16732
17570
|
cachedVersion = "0.0.0";
|
|
16733
17571
|
return cachedVersion;
|
|
16734
17572
|
}
|
|
17573
|
+
var cachedVersion;
|
|
17574
|
+
var init_version = __esm({
|
|
17575
|
+
"src/version.ts"() {
|
|
17576
|
+
"use strict";
|
|
17577
|
+
}
|
|
17578
|
+
});
|
|
16735
17579
|
|
|
16736
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";
|
|
16737
17593
|
var tools = [
|
|
16738
17594
|
{
|
|
16739
17595
|
name: "ntrp_setup_check",
|