@sonnechasser/ntrp 1.5.3 → 1.5.7

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.
Files changed (3) hide show
  1. package/dist/index.js +1853 -283
  2. package/dist/mcp/server.js +1565 -70
  3. package/package.json +2 -1
@@ -74,6 +74,17 @@ function formatCurrency(value) {
74
74
  if (value >= 1e3) return `$${(value / 1e3).toFixed(0)}K`;
75
75
  return `$${value.toFixed(0)}`;
76
76
  }
77
+ function formatConstraintLine(aggregate) {
78
+ if (!aggregate) return void 0;
79
+ const gating = aggregate.gating_vital_sign;
80
+ const label = VITAL_SIGN_LABELS[gating] ?? gating;
81
+ const vital = aggregate.vital_signs.find((v) => v.vital_sign === gating);
82
+ if (vital?.dollar_value != null && vital.dollar_value > 0) {
83
+ const dollars = `${formatCurrency(vital.dollar_value)} ${vital.dollar_label ?? ""}`.trim();
84
+ return `${label} (${dollars})`;
85
+ }
86
+ return label;
87
+ }
77
88
  function extractPipelineMetrics(vitals) {
78
89
  const flowRate = vitals.find((v) => v.vital_sign === "flow_rate");
79
90
  if (!flowRate) return null;
@@ -122,6 +133,7 @@ __export(store_exports, {
122
133
  getExportsDir: () => getExportsDir,
123
134
  getKnowledgeDir: () => getKnowledgeDir,
124
135
  getMemoryDir: () => getMemoryDir,
136
+ getRuminationsDir: () => getRuminationsDir,
125
137
  getStrategiesDir: () => getStrategiesDir,
126
138
  getWinsDir: () => getWinsDir,
127
139
  loadConfig: () => loadConfig,
@@ -265,6 +277,22 @@ from work done outside this platform.
265
277
  }
266
278
  return dir;
267
279
  }
280
+ function getRuminationsDir() {
281
+ const dir = join(NTRP_DIR, "ruminations");
282
+ if (!existsSync(dir)) {
283
+ mkdirSync(dir, { recursive: true });
284
+ writeFileSync(
285
+ join(dir, "README.md"),
286
+ `# Craft logs
287
+
288
+ This directory stores overnight strategy craft jobs. Each job has a machine file (\`<id>.json\`) and a short log (\`<id>.md\`).
289
+
290
+ Resume a job with \`ntrp strategy craft --resume <id>\`.
291
+ `
292
+ );
293
+ }
294
+ return dir;
295
+ }
268
296
  function getWinsDir() {
269
297
  const dir = join(NTRP_DIR, "wins");
270
298
  if (!existsSync(dir)) {
@@ -568,6 +596,14 @@ var init_path_safety = __esm({
568
596
  });
569
597
 
570
598
  // src/services/export-kinds.ts
599
+ function archiveSubdirForKind(kind) {
600
+ if (kind.startsWith("prompt:")) return "handoffs";
601
+ if (kind === "report") return "reports";
602
+ if (kind === "notes") return "notes";
603
+ if (kind === "csv") return "csv";
604
+ if (kind === "publish") return "publish";
605
+ return "handoffs";
606
+ }
571
607
  function latestBasenameForKind(kind) {
572
608
  if (kind.startsWith("prompt:")) {
573
609
  const target = kind.slice("prompt:".length);
@@ -967,6 +1003,9 @@ import {
967
1003
  } from "fs";
968
1004
  import { basename as basename2, dirname as dirname2, join as join3, resolve as resolve3, sep as sep2 } from "path";
969
1005
  import { randomUUID } from "crypto";
1006
+ function exportStamp(d = /* @__PURE__ */ new Date()) {
1007
+ return d.toISOString().replace(/T/, "-").replace(/:/g, "").slice(0, 15);
1008
+ }
970
1009
  function ensureExportsLayout(root = getExportsDir()) {
971
1010
  mkdirSync4(root, { recursive: true });
972
1011
  mkdirSync4(join3(root, "latest"), { recursive: true });
@@ -985,6 +1024,15 @@ function ensureExportsLayout(root = getExportsDir()) {
985
1024
  }
986
1025
  return root;
987
1026
  }
1027
+ function getArchiveKindDir(kind) {
1028
+ const root = ensureExportsLayout();
1029
+ const dir = join3(root, archiveSubdirForKind(kind));
1030
+ mkdirSync4(dir, { recursive: true });
1031
+ return dir;
1032
+ }
1033
+ function resolveArchivePath(kind, filename) {
1034
+ return join3(getArchiveKindDir(kind), filename);
1035
+ }
988
1036
  function getAiInboxDir() {
989
1037
  return getConfiguredAiInboxDir();
990
1038
  }
@@ -1043,6 +1091,16 @@ function listExports(opts = {}) {
1043
1091
  }
1044
1092
  return items.slice(0, limit);
1045
1093
  }
1094
+ function updateArchiveLatest(kind, sourcePath, root) {
1095
+ const latestDir = join3(root, "latest");
1096
+ mkdirSync4(latestDir, { recursive: true });
1097
+ const name = latestBasenameForKind(kind);
1098
+ const dest = join3(latestDir, name);
1099
+ copyPath(sourcePath, dest);
1100
+ if (kind.startsWith("prompt:")) {
1101
+ copyPath(sourcePath, join3(latestDir, "handoff.md"));
1102
+ }
1103
+ }
1046
1104
  function copyPath(src, dest) {
1047
1105
  mkdirSync4(dirname2(dest), { recursive: true });
1048
1106
  if (existsSync3(dest)) {
@@ -1240,6 +1298,29 @@ function syncRecentToInbox(limit = 10) {
1240
1298
  if (newest) persistHandoffSkillFiles(newest);
1241
1299
  return n;
1242
1300
  }
1301
+ function recordExportWrite(opts) {
1302
+ const root = ensureExportsLayout();
1303
+ const path = resolve3(opts.path);
1304
+ if (!existsSync3(path)) {
1305
+ throw new Error(`Export path does not exist: ${path}`);
1306
+ }
1307
+ updateArchiveLatest(opts.kind, path, root);
1308
+ const event = {
1309
+ id: randomUUID().slice(0, 8),
1310
+ op: "write",
1311
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1312
+ kind: opts.kind,
1313
+ path,
1314
+ session_id: opts.sessionId,
1315
+ title: opts.title
1316
+ };
1317
+ persistHandoffSkillFiles(event);
1318
+ const inboxPath = syncAiInbox({ kind: opts.kind, path });
1319
+ if (inboxPath) event.inbox_path = inboxPath;
1320
+ appendManifestEvent(event, root);
1321
+ regenerateIndex(root);
1322
+ return event;
1323
+ }
1243
1324
  function archiveIndexPath() {
1244
1325
  return join3(ensureExportsLayout(), "INDEX.md");
1245
1326
  }
@@ -1380,6 +1461,8 @@ function buildSessionContextDoc(file, opts = {}) {
1380
1461
  lines.push(`- Constraints: ${file.strategist.constraintsNote}`);
1381
1462
  }
1382
1463
  if (file.strategist.origin) lines.push(`- Origin: ${file.strategist.origin}`);
1464
+ if (file.strategist.mode) lines.push(`- Mode: ${file.strategist.mode}`);
1465
+ if (file.strategist.ruminationId) lines.push(`- Craft job: ${file.strategist.ruminationId}`);
1383
1466
  lines.push("");
1384
1467
  }
1385
1468
  if (file.think) {
@@ -5106,6 +5189,7 @@ var init_surfaces = __esm({
5106
5189
  strategy: { defaultTier: "medium", allowUserTier: false },
5107
5190
  strategist: { defaultTier: "high", allowUserTier: true },
5108
5191
  strategist_stress: { defaultTier: "high", allowUserTier: false },
5192
+ strategist_critic: { defaultTier: "high", allowUserTier: false },
5109
5193
  option_eval: { defaultTier: "low", allowUserTier: false }
5110
5194
  };
5111
5195
  }
@@ -7267,12 +7351,51 @@ import { stringify as stringifyYaml } from "yaml";
7267
7351
  function strategyLibraryPath(slug) {
7268
7352
  return join16(getStrategiesDir(), `${slug}.md`);
7269
7353
  }
7270
- function writeStrategyMarkdown(strategy) {
7354
+ function writeStrategyMarkdown(strategy, extras) {
7271
7355
  const path = strategyLibraryPath(strategy.slug);
7272
- writeFileSync11(path, renderStrategyMarkdown(strategy), "utf-8");
7356
+ writeFileSync11(path, renderStrategyMarkdown(strategy, extras), "utf-8");
7273
7357
  return path;
7274
7358
  }
7275
- function renderStrategyMarkdown(strategy) {
7359
+ function formatEffortSum(workstreams) {
7360
+ const sum = workstreams.reduce(
7361
+ (acc, ws) => acc + (Number.isFinite(ws.effort_hours) ? ws.effort_hours : 0),
7362
+ 0
7363
+ );
7364
+ const count = workstreams.length;
7365
+ return `~${Math.round(sum)} team-hours across ${count} workstream${count === 1 ? "" : "s"}`;
7366
+ }
7367
+ function renderConstraintHeading(constraintLine) {
7368
+ return `## Constraint
7369
+ ${constraintLine?.trim() || "none stated"}
7370
+ `;
7371
+ }
7372
+ function renderScopeHeading(constraints, outOfScope) {
7373
+ const inLines = constraints.length > 0 ? formatList(constraints) : "- none stated";
7374
+ const outItems = (outOfScope ?? []).map((s) => s.trim()).filter(Boolean);
7375
+ const outLines = outItems.length > 0 ? formatList(outItems) : "- none stated";
7376
+ return `## In scope / out of scope
7377
+ In scope:
7378
+ ${inLines}
7379
+
7380
+ Out of scope:
7381
+ ${outLines}
7382
+ `;
7383
+ }
7384
+ function renderKilledAlternativeLine(killedAlternative) {
7385
+ return `Killed alternative: ${killedAlternative?.trim() || "none stated"}`;
7386
+ }
7387
+ function renderEffortHeading(workstreams) {
7388
+ return `## Effort
7389
+ ${formatEffortSum(workstreams)}
7390
+ `;
7391
+ }
7392
+ function renderReviewHeading(opts) {
7393
+ const cmd = opts.slug?.trim() ? `/strategy review ${opts.slug.trim()}` : "/strategy review";
7394
+ return `## Review
7395
+ Cadence: ${opts.cadence}. Check progress with ${cmd}.
7396
+ `;
7397
+ }
7398
+ function renderStrategyMarkdown(strategy, extras) {
7276
7399
  const frontmatter = stringifyYaml({
7277
7400
  id: strategy.id,
7278
7401
  slug: strategy.slug,
@@ -7287,6 +7410,9 @@ function renderStrategyMarkdown(strategy) {
7287
7410
  ...strategy.baseline_batch_id ? { baseline_batch_id: strategy.baseline_batch_id } : {},
7288
7411
  updated_at: strategy.updated_at
7289
7412
  }).trim();
7413
+ if (strategy.origin === "strategist") {
7414
+ return renderStrategistLibraryMarkdown(strategy, extras, frontmatter);
7415
+ }
7290
7416
  const objectiveSection = strategy.objective ? `
7291
7417
  ## Objective
7292
7418
  ${strategy.objective}
@@ -7333,6 +7459,67 @@ ${formatList(strategy.risks)}
7333
7459
  ## Experiment Design
7334
7460
  ${strategy.experiment_design}
7335
7461
 
7462
+ ## Source Excerpt
7463
+ ${strategy.raw_excerpt || "_No excerpt captured._"}
7464
+ `;
7465
+ }
7466
+ function renderStrategistLibraryMarkdown(strategy, extras, frontmatter) {
7467
+ const callSection = strategy.raw_excerpt ? `
7468
+ ## The Call
7469
+ ${strategy.raw_excerpt}
7470
+ ` : "";
7471
+ const objectiveSection = strategy.objective ? `
7472
+ ## Locked objective
7473
+ ${strategy.objective}
7474
+ ` : "";
7475
+ const workstreamSection = strategy.workstreams.length > 0 ? `
7476
+ ## Workstreams
7477
+ ${strategy.workstreams.map(formatWorkstream).join("\n")}
7478
+ ` : "";
7479
+ const assumptionsSection = strategy.assumptions.length > 0 ? `
7480
+ ## Assumptions (unverified)
7481
+ ${formatList(strategy.assumptions)}
7482
+ ` : "";
7483
+ const craftLogSection = extras?.craftLogPath ? `
7484
+ ## Craft log
7485
+ See \`${extras.craftLogPath}\`.
7486
+ ` : "";
7487
+ return `---
7488
+ ${frontmatter}
7489
+ ---
7490
+
7491
+ # ${strategy.title}
7492
+ ${callSection}${objectiveSection}
7493
+ ${renderConstraintHeading(extras?.constraintLine)}
7494
+ ## Goal
7495
+ ${strategy.goal}
7496
+
7497
+ ${renderScopeHeading(strategy.constraints, extras?.outOfScope)}
7498
+ ## Hypothesis
7499
+ ${strategy.hypothesis}
7500
+
7501
+ ${renderKilledAlternativeLine(extras?.killedAlternative)}
7502
+
7503
+ ## Target Segment
7504
+ ${strategy.target_segment}
7505
+
7506
+ ${renderEffortHeading(strategy.workstreams)}${workstreamSection}
7507
+ ## Success Metrics
7508
+ ${formatMetrics(strategy.success_metrics)}
7509
+
7510
+ ## Leading Indicators
7511
+ ${formatMetrics(strategy.leading_indicators)}
7512
+
7513
+ ## Recommended Actions
7514
+ ${formatList(strategy.recommended_actions)}
7515
+ ${assumptionsSection}
7516
+ ## Risks
7517
+ ${formatList(strategy.risks)}
7518
+
7519
+ ## Experiment Design
7520
+ ${strategy.experiment_design}
7521
+
7522
+ ${renderReviewHeading({ cadence: strategy.review_cadence, slug: strategy.slug })}${craftLogSection}
7336
7523
  ## Source Excerpt
7337
7524
  ${strategy.raw_excerpt || "_No excerpt captured._"}
7338
7525
  `;
@@ -8203,6 +8390,7 @@ function buildSessionFileSnapshot(ctx) {
8203
8390
  if (ctx.strategistState) file.strategist = ctx.strategistState;
8204
8391
  if (ctx.thinkState) file.think = ctx.thinkState;
8205
8392
  if (ctx.pendingAsk) file.pending_ask = ctx.pendingAsk;
8393
+ if (ctx.lastCraftJobId) file.last_craft_job_id = ctx.lastCraftJobId;
8206
8394
  return file;
8207
8395
  }
8208
8396
  function defaultSessionAnalysis(primary = "gtm_health") {
@@ -8557,6 +8745,9 @@ async function finalizeSession(ctx, stage) {
8557
8745
  if (ctx.pendingAsk) {
8558
8746
  file.pending_ask = ctx.pendingAsk;
8559
8747
  }
8748
+ if (ctx.lastCraftJobId) {
8749
+ file.last_craft_job_id = ctx.lastCraftJobId;
8750
+ }
8560
8751
  try {
8561
8752
  writeFileSync13(ctx.sessionFile, JSON.stringify(file, null, 2) + "\n");
8562
8753
  } catch {
@@ -8628,6 +8819,7 @@ function resetContextForSwitch(ctx, opts) {
8628
8819
  ctx.strategistState = opts.strategistState;
8629
8820
  ctx.thinkState = opts.thinkState;
8630
8821
  ctx.pendingAsk = opts.pendingAsk;
8822
+ ctx.lastCraftJobId = opts.lastCraftJobId;
8631
8823
  ctx.gapAudit = void 0;
8632
8824
  ctx.deliverIntent = false;
8633
8825
  ctx.computeInProgress = false;
@@ -10254,7 +10446,7 @@ This command is hidden. Type \`/ingest --demo\` instead. That command calls this
10254
10446
  name: strategy
10255
10447
  description: Make a measured strategy from your data
10256
10448
  section: More
10257
- args: [objective] | [list|show|review|ingest|add|sync|sources] [args]
10449
+ args: [objective] | [list|show|review|ingest|add|sync|sources|craft] [args]
10258
10450
  handler: ../commands/strategy.ts
10259
10451
  ---
10260
10452
 
@@ -10263,6 +10455,8 @@ NTRP uses live data. It works back from the objective.
10263
10455
  The result is sequenced workstreams with dated milestones, deliverables, outcome ranges, and a contingency per workstream.
10264
10456
  Saved plans go to the strategy library. Later answers use them.
10265
10457
  Type \`/strategy review [slug]\` to check the plan against live data when new batches arrive.
10458
+ Type \`keep going\` at the confirm card to keep working until the plan is ready.
10459
+ One-shot: \`ntrp strategy craft <objective>\`. Resume with \`ntrp strategy craft --resume <id>\`.
10266
10460
 
10267
10461
  Library commands: \`/strategy list\`, \`/strategy show <slug>\`.
10268
10462
  Type \`/strategy ingest <file>\` for markdown, YAML, PDF, text, or \`-\` for stdin.
@@ -14229,14 +14423,140 @@ var init_registry2 = __esm({
14229
14423
  }
14230
14424
  });
14231
14425
 
14426
+ // src/ruminations/store.ts
14427
+ import { existsSync as existsSync24, readFileSync as readFileSync21, writeFileSync as writeFileSync15 } from "fs";
14428
+ import { join as join24 } from "path";
14429
+ import { randomUUID as randomUUID8 } from "crypto";
14430
+ function makeRuminationId(now2 = /* @__PURE__ */ new Date()) {
14431
+ const day = now2.toISOString().slice(0, 10).replace(/-/g, "");
14432
+ return `${day}-${randomUUID8().slice(0, 4)}`;
14433
+ }
14434
+ function ruminationJsonPath(id) {
14435
+ return join24(getRuminationsDir(), `${id}.json`);
14436
+ }
14437
+ function ruminationLogPath(id) {
14438
+ return join24(getRuminationsDir(), `${id}.md`);
14439
+ }
14440
+ function unfinishedCraftJob(id) {
14441
+ if (!id?.trim()) return null;
14442
+ const job = loadRuminationJob(id);
14443
+ if (!job || job.status === "ready") return null;
14444
+ return job;
14445
+ }
14446
+ function loadRuminationJob(id) {
14447
+ const path = ruminationJsonPath(id);
14448
+ if (!existsSync24(path)) return null;
14449
+ try {
14450
+ const parsed = JSON.parse(readFileSync21(path, "utf-8"));
14451
+ if (!parsed || typeof parsed !== "object" || parsed.id !== id) return null;
14452
+ if (parsed.best_plan === void 0) parsed.best_plan = parsed.plan ?? null;
14453
+ if (parsed.best_score === void 0) parsed.best_score = parsed.last_critic?.score ?? null;
14454
+ return parsed;
14455
+ } catch {
14456
+ return null;
14457
+ }
14458
+ }
14459
+ function createRuminationJob(opts) {
14460
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
14461
+ return {
14462
+ id: opts.id ?? makeRuminationId(),
14463
+ objective: opts.objective,
14464
+ constraints_note: opts.constraintsNote,
14465
+ baseline_batch_id: opts.baselineBatchId ?? null,
14466
+ snapshot_hash: opts.snapshotHash,
14467
+ plan: null,
14468
+ best_plan: null,
14469
+ best_score: null,
14470
+ last_critic: null,
14471
+ rubric_gaps: [],
14472
+ iterations: [],
14473
+ cumulative_input_tokens: 0,
14474
+ cumulative_output_tokens: 0,
14475
+ status: "running",
14476
+ from_fallback: false,
14477
+ revised_once: false,
14478
+ extra_ground_used: false,
14479
+ min_score: opts.minScore,
14480
+ max_rounds: opts.maxRounds,
14481
+ max_tokens: opts.maxTokens,
14482
+ created_at: now2,
14483
+ updated_at: now2,
14484
+ no_improve_streak: 0,
14485
+ last_critic_score: null
14486
+ };
14487
+ }
14488
+ function renderRuminationLog(job) {
14489
+ const lines = [];
14490
+ lines.push(`# Craft log \u2014 ${job.id}`);
14491
+ lines.push("");
14492
+ lines.push(`Objective: ${job.objective}`);
14493
+ lines.push(`Status: ${job.status}${job.stop_reason ? ` (${job.stop_reason})` : ""}`);
14494
+ lines.push(
14495
+ `Tokens: ${job.cumulative_input_tokens + job.cumulative_output_tokens} total (${job.cumulative_input_tokens} in / ${job.cumulative_output_tokens} out)`
14496
+ );
14497
+ if (job.library_path) lines.push(`Strategy: ${job.library_path}`);
14498
+ if (job.handoff_path) lines.push(`Handoff: ${job.handoff_path}`);
14499
+ lines.push("");
14500
+ lines.push("## Rounds");
14501
+ lines.push("");
14502
+ if (job.iterations.length === 0) {
14503
+ lines.push("_No rounds yet._");
14504
+ } else {
14505
+ for (const it of job.iterations) {
14506
+ const codes = it.blocking_codes.length > 0 ? it.blocking_codes.join(", ") : "none";
14507
+ lines.push(
14508
+ `- Round ${it.round}: score ${it.critic_score ?? "\u2014"} \xB7 ${it.decision} \xB7 blocking ${codes} \xB7 ${it.tokens_in + it.tokens_out} tok`
14509
+ );
14510
+ }
14511
+ }
14512
+ if (job.last_critic?.partner_pushback) {
14513
+ lines.push("");
14514
+ lines.push("## Partner pushback");
14515
+ lines.push("");
14516
+ lines.push(job.last_critic.partner_pushback);
14517
+ }
14518
+ if (job.last_critic?.killed_alternative) {
14519
+ lines.push("");
14520
+ lines.push("## Killed alternative");
14521
+ lines.push("");
14522
+ lines.push(job.last_critic.killed_alternative);
14523
+ }
14524
+ lines.push("");
14525
+ return lines.join("\n");
14526
+ }
14527
+ function saveRuminationJob(job) {
14528
+ job.updated_at = (/* @__PURE__ */ new Date()).toISOString();
14529
+ getRuminationsDir();
14530
+ writeFileSync15(ruminationJsonPath(job.id), JSON.stringify(job, null, 2) + "\n", "utf-8");
14531
+ writeFileSync15(ruminationLogPath(job.id), renderRuminationLog(job), "utf-8");
14532
+ }
14533
+ function addRuminationIteration(job, iteration) {
14534
+ job.iterations.push({ ...iteration, at: (/* @__PURE__ */ new Date()).toISOString() });
14535
+ job.cumulative_input_tokens += iteration.tokens_in;
14536
+ job.cumulative_output_tokens += iteration.tokens_out;
14537
+ }
14538
+ var init_store3 = __esm({
14539
+ "src/ruminations/store.ts"() {
14540
+ "use strict";
14541
+ init_store();
14542
+ }
14543
+ });
14544
+
14232
14545
  // src/conversation/recommended-action.ts
14233
14546
  function resolveRecommendedAction(ctx) {
14234
14547
  if (!hasValidLicense()) return { submit: "/activate", hint: "/activate" };
14235
14548
  const phase = resolveConversationPhase(ctx);
14236
14549
  switch (phase) {
14237
14550
  case "explore":
14551
+ if (!canUseReplAi(ctx)) {
14552
+ if (ctx.stage === "delivered") return { submit: "/end", hint: "home" };
14553
+ return { submit: "/connect", hint: "/connect" };
14554
+ }
14555
+ if (unfinishedCraftJob(ctx.lastCraftJobId)) {
14556
+ return { submit: "keep going", hint: "keep going" };
14557
+ }
14238
14558
  if (ctx.stage === "delivered") return { submit: "/end", hint: "home" };
14239
- return canUseReplAi(ctx) ? null : { submit: "/connect", hint: "/connect" };
14559
+ return null;
14240
14560
  case "awaiting_data":
14241
14561
  if (ctx.gapAudit?.can_compute) return { submit: "go ahead", hint: "go ahead" };
14242
14562
  if (!sessionHasData(ctx)) return { submit: "use demo data", hint: "use demo data" };
@@ -14260,6 +14580,7 @@ var init_recommended_action = __esm({
14260
14580
  init_activation();
14261
14581
  init_registry2();
14262
14582
  init_phase();
14583
+ init_store3();
14263
14584
  }
14264
14585
  });
14265
14586
 
@@ -15966,11 +16287,11 @@ function jsonSafe(value) {
15966
16287
  }
15967
16288
  return value;
15968
16289
  }
15969
- function envelope(command, data, warnings) {
16290
+ function envelope(command, data, warnings, status = "ok") {
15970
16291
  return {
15971
16292
  schema_version: HEADLESS_SCHEMA_VERSION,
15972
16293
  command,
15973
- status: "ok",
16294
+ status,
15974
16295
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
15975
16296
  data,
15976
16297
  ...warnings && warnings.length > 0 ? { warnings } : {}
@@ -15986,8 +16307,8 @@ function errorEnvelope(command, err) {
15986
16307
  error: ntrpError.toHeadlessError()
15987
16308
  };
15988
16309
  }
15989
- function emitResult(command, data, warnings) {
15990
- console.log(JSON.stringify(jsonSafe(envelope(command, data, warnings)), null, 2));
16310
+ function emitResult(command, data, warnings, status = "ok") {
16311
+ console.log(JSON.stringify(jsonSafe(envelope(command, data, warnings, status)), null, 2));
15991
16312
  }
15992
16313
  function emitError(command, err) {
15993
16314
  const ntrpError = err instanceof NtrpError ? err : toNtrpError(err);
@@ -17587,7 +17908,13 @@ async function persistStrategistPlan(plan, opts = {}) {
17587
17908
  if (!strategy) {
17588
17909
  throw new NtrpError("strategy_persist_failed", "Strategy was not found after saving.", 1 /* RuntimeError */);
17589
17910
  }
17590
- const writtenPath = writeStrategyMarkdown(strategy);
17911
+ const extras = {
17912
+ craftLogPath: opts.craftLogPath,
17913
+ constraintLine: opts.constraintLine,
17914
+ killedAlternative: opts.killedAlternative,
17915
+ outOfScope: opts.outOfScope
17916
+ };
17917
+ const writtenPath = writeStrategyMarkdown(strategy, extras);
17591
17918
  await insertStrategySource({
17592
17919
  strategy_id: strategy.id,
17593
17920
  source_type: "agent",
@@ -17615,7 +17942,6 @@ var init_strategist = __esm({
17615
17942
  init_errors2();
17616
17943
  init_types2();
17617
17944
  init_formatters();
17618
- init_formatters();
17619
17945
  }
17620
17946
  });
17621
17947
 
@@ -17901,6 +18227,52 @@ ${AAR_BLOCK}
17901
18227
 
17902
18228
  Then respond with the FINAL revised plan as strict JSON in the same schema \u2014 no markdown fences, no prose. Fold what you learned into constraints, assumptions, risks, and confidence. This version is the one that gets saved and reviewed against, so make every number one you are willing to be checked on.`;
17903
18229
  }
18230
+ function buildCriticMessage(input) {
18231
+ return `CRAFT CRITIC. You are a skeptical partner reviewing a GTM plan before it ships to a client. The objective is locked and must be echoed verbatim.
18232
+
18233
+ ARMED OBJECTIVE (byte-identical; do not rewrite):
18234
+ "${input.objective}"
18235
+
18236
+ CODE RUBRIC GAPS (already blocking \u2014 you cannot ship until these are gone):
18237
+ ${input.rubricGaps || "(none)"}
18238
+
18239
+ LIVE SNAPSHOT (cite these numbers only):
18240
+ ${input.healthSnapshot}
18241
+
18242
+ CURRENT PLAN JSON:
18243
+ ${input.planJson}
18244
+
18245
+ ${HEILMEIER_GATE}
18246
+
18247
+ ${AAR_BLOCK}
18248
+
18249
+ Decide whether a partner would ship this. Respond with STRICT JSON only:
18250
+ {
18251
+ "ship": false,
18252
+ "score": 0,
18253
+ "objective_echo": ${JSON.stringify(input.objective)},
18254
+ "gaps": [{ "code": "bottleneck", "severity": "blocking", "note": "...", "fix": "..." }],
18255
+ "killed_alternative": "one approach that failed newness/stake/exams",
18256
+ "partner_pushback": "one paragraph a partner would actually say"
18257
+ }
18258
+
18259
+ ship may be true only when objective_echo matches, blocking gaps are empty, and you would defend this plan in the room. Advisory gaps do not block. Score 0-100.`;
18260
+ }
18261
+ function buildReviseMessage(input) {
18262
+ return `CRAFT REVISE. Keep the objective verbatim. Patch only the listed gap codes. Copy baselines from the current plan / snapshot exactly \u2014 do not invent numbers.
18263
+
18264
+ ARMED OBJECTIVE (must appear unchanged as "objective"):
18265
+ "${input.objective}"
18266
+
18267
+ GAPS TO CLOSE:
18268
+ ${input.gapsJson}
18269
+
18270
+ CURRENT PLAN:
18271
+ ${input.planJson}
18272
+
18273
+ Respond with ONLY the full plan JSON object in this schema \u2014 no prose, no fences:
18274
+ ${STRATEGIST_PLAN_SCHEMA_BLOCK}`;
18275
+ }
17904
18276
  var STRATEGIST_PLAN_SCHEMA_BLOCK;
17905
18277
  var init_strategist_prompt = __esm({
17906
18278
  "src/ai/strategist-prompt.ts"() {
@@ -18428,6 +18800,297 @@ var init_strategist_validate = __esm({
18428
18800
  }
18429
18801
  });
18430
18802
 
18803
+ // src/ai/strategist-rubric.ts
18804
+ function numbersMatch2(a, b) {
18805
+ if (a === b) return true;
18806
+ if (a === 0 || b === 0) return Math.abs(a - b) < 0.5;
18807
+ return Math.abs(a - b) / Math.max(Math.abs(a), Math.abs(b)) <= 0.02;
18808
+ }
18809
+ function parseIsoDate2(value) {
18810
+ const match = value.trim().match(/^(\d{4})-(\d{2})-(\d{2})/);
18811
+ if (!match) return null;
18812
+ const date = /* @__PURE__ */ new Date(`${match[1]}-${match[2]}-${match[3]}T00:00:00Z`);
18813
+ return Number.isNaN(date.getTime()) ? null : date;
18814
+ }
18815
+ function addDays2(date, days) {
18816
+ const out = new Date(date);
18817
+ out.setUTCDate(out.getUTCDate() + days);
18818
+ return out;
18819
+ }
18820
+ function workstreamText(ws) {
18821
+ return [ws.title, ws.problem, ws.rationale, ws.actions.join(" "), ws.play_ids.join(" ")].join(" ");
18822
+ }
18823
+ function hasOwnerShape(ws) {
18824
+ const blob = workstreamText(ws).toLowerCase();
18825
+ return OWNER_TOKENS.some((token) => blob.includes(token));
18826
+ }
18827
+ function firstWorkstreamMovesConstraint(ws, gating) {
18828
+ const plays = getPlaybook();
18829
+ if (ws.play_ids.some((id) => plays.find((p) => p.id === id)?.trigger_vital_sign === gating)) {
18830
+ return true;
18831
+ }
18832
+ const blob = workstreamText(ws).toLowerCase();
18833
+ return VITAL_ALIASES[gating].some((alias) => blob.includes(alias));
18834
+ }
18835
+ function citesStake(plan, snapshot) {
18836
+ const dollars = [];
18837
+ if (snapshot.aggregate.total_value_at_risk != null && snapshot.aggregate.total_value_at_risk > 0) {
18838
+ dollars.push(snapshot.aggregate.total_value_at_risk);
18839
+ }
18840
+ for (const vs of snapshot.aggregate.vital_signs) {
18841
+ if (vs.dollar_value != null && vs.dollar_value > 0) dollars.push(vs.dollar_value);
18842
+ }
18843
+ if (dollars.length === 0) return true;
18844
+ const blob = `${plan.summary_30k} ${plan.hypothesis} ${plan.workstreams[0]?.problem ?? ""}`;
18845
+ const found = extractNumbers(blob);
18846
+ if (found.some((n) => dollars.some((d) => numbersMatch2(n, d)))) return true;
18847
+ const formatted = dollars.map((d) => formatCurrency(d).toLowerCase());
18848
+ const lower = blob.toLowerCase();
18849
+ return formatted.some((f) => lower.includes(f.toLowerCase()));
18850
+ }
18851
+ function hasStartWithin48h(plan, todayIso) {
18852
+ const today = parseIsoDate2(todayIso) ?? /* @__PURE__ */ new Date(`${todayIso}T00:00:00Z`);
18853
+ const limit = addDays2(today, 2);
18854
+ const first = plan.workstreams[0];
18855
+ if (!first) return false;
18856
+ if (first.actions.some((action) => START_SOON_RE.test(action))) return true;
18857
+ const dated = [
18858
+ ...first.milestones.map((m) => m.due),
18859
+ ...first.deliverables.map((d) => d.due)
18860
+ ];
18861
+ for (const iso of dated) {
18862
+ const parsed = parseIsoDate2(iso);
18863
+ if (parsed && parsed.getTime() <= limit.getTime()) return true;
18864
+ }
18865
+ return plan.workstreams.some(
18866
+ (ws) => ws.actions.some((action) => START_SOON_RE.test(action))
18867
+ );
18868
+ }
18869
+ function examsHold(ws) {
18870
+ const o = ws.expected_outcome;
18871
+ return extractNumbers(o.baseline).length > 0 && extractNumbers(o.target_range).length > 0 && isKnownInstrument(o.measured_by);
18872
+ }
18873
+ function inventedNumbers(plan, evidenceText) {
18874
+ const evidenceNums = extractNumbers(evidenceText);
18875
+ if (evidenceNums.length === 0) return false;
18876
+ for (const ws of plan.workstreams) {
18877
+ const baselines = extractNumbers(ws.expected_outcome.baseline);
18878
+ if (baselines.length === 0) continue;
18879
+ if (!baselines.some((b) => evidenceNums.some((e) => numbersMatch2(b, e)))) return true;
18880
+ }
18881
+ return false;
18882
+ }
18883
+ function scoreConsultantPlan(plan, opts) {
18884
+ const gaps = [];
18885
+ const gating = opts.snapshot.aggregate.gating_vital_sign;
18886
+ const first = plan.workstreams[0];
18887
+ const evidence = opts.evidenceText ?? JSON.stringify({
18888
+ aggregate: opts.snapshot.aggregate
18889
+ });
18890
+ if (opts.armedObjective && plan.objective !== opts.armedObjective) {
18891
+ gaps.push({
18892
+ code: "objective_drift",
18893
+ severity: "blocking",
18894
+ note: "Plan objective does not match the armed objective.",
18895
+ fix: "Copy the armed objective into plan.objective with no edits."
18896
+ });
18897
+ }
18898
+ if (!first || !firstWorkstreamMovesConstraint(first, gating)) {
18899
+ const label = VITAL_SIGN_LABELS[gating] ?? gating;
18900
+ gaps.push({
18901
+ code: "bottleneck",
18902
+ severity: "blocking",
18903
+ note: `First workstream does not move the gating vital (${label}).`,
18904
+ fix: `Lead with a play or problem that moves ${gating}. Non-constraint work waits.`
18905
+ });
18906
+ }
18907
+ if (!citesStake(plan, opts.snapshot)) {
18908
+ gaps.push({
18909
+ code: "stake",
18910
+ severity: "blocking",
18911
+ note: "30k or hypothesis does not cite snapshot dollars / cost of inaction.",
18912
+ fix: "Open with the gating dollar figure from the live snapshot."
18913
+ });
18914
+ }
18915
+ for (const ws of plan.workstreams) {
18916
+ if (!hasOwnerShape(ws)) {
18917
+ gaps.push({
18918
+ code: "owner",
18919
+ severity: "blocking",
18920
+ note: `Workstream "${ws.title}" has no function-shaped owner.`,
18921
+ fix: "Name RevOps, sales manager, CS lead, marketing ops, or AE lead in the actions."
18922
+ });
18923
+ break;
18924
+ }
18925
+ }
18926
+ if (!hasStartWithin48h(plan, opts.todayIso)) {
18927
+ gaps.push({
18928
+ code: "start_48h",
18929
+ severity: "blocking",
18930
+ note: "No action or milestone is startable within 48 hours.",
18931
+ fix: "Put a dated first action or milestone within two days of today."
18932
+ });
18933
+ }
18934
+ const missingEffort = plan.workstreams.filter(
18935
+ (ws) => !Number.isFinite(ws.effort_hours) || ws.effort_hours <= 0
18936
+ );
18937
+ if (missingEffort.length > 0 || plan.workstreams.length === 0) {
18938
+ gaps.push({
18939
+ code: "effort",
18940
+ severity: "blocking",
18941
+ note: "Each workstream needs a finite effort_hours greater than zero.",
18942
+ fix: "Set effort_hours to a real team-hour estimate per workstream."
18943
+ });
18944
+ }
18945
+ if (opts.constraintsNote?.trim() && plan.constraints.length === 0) {
18946
+ gaps.push({
18947
+ code: "scope",
18948
+ severity: "blocking",
18949
+ note: "Operator stated constraints, but the plan constraints list is empty.",
18950
+ fix: "Copy operator constraints into plan.constraints. Name what is out of scope."
18951
+ });
18952
+ }
18953
+ if (plan.workstreams.some((ws) => !examsHold(ws))) {
18954
+ gaps.push({
18955
+ code: "exams",
18956
+ severity: "blocking",
18957
+ note: "A workstream expected outcome is missing a numeric baseline, range, or known instrument.",
18958
+ fix: "Give every expected_outcome a baseline, target range, and measured_by instrument from live data."
18959
+ });
18960
+ }
18961
+ if (inventedNumbers(plan, evidence)) {
18962
+ gaps.push({
18963
+ code: "invented_numbers",
18964
+ severity: "blocking",
18965
+ note: "An outcome baseline is not in the grounded snapshot.",
18966
+ fix: "Copy baselines from the health snapshot exactly."
18967
+ });
18968
+ }
18969
+ const blocking = gaps.filter((g) => g.severity === "blocking");
18970
+ return { pass: blocking.length === 0, gaps, blocking };
18971
+ }
18972
+ function asGapCode(value) {
18973
+ return typeof value === "string" && GAP_CODES.includes(value) ? value : null;
18974
+ }
18975
+ function parseCriticResponse(raw, opts) {
18976
+ if (!raw) {
18977
+ return {
18978
+ ship: false,
18979
+ score: 0,
18980
+ objective_echo: "",
18981
+ gaps: [
18982
+ {
18983
+ code: "thin_evidence",
18984
+ severity: "blocking",
18985
+ note: "Critic reply was not valid JSON.",
18986
+ fix: "Respond with the critic JSON object only."
18987
+ }
18988
+ ],
18989
+ killed_alternative: "",
18990
+ partner_pushback: ""
18991
+ };
18992
+ }
18993
+ const objectiveEcho = typeof raw.objective_echo === "string" ? raw.objective_echo : "";
18994
+ const killed = typeof raw.killed_alternative === "string" ? raw.killed_alternative.trim() : "";
18995
+ const pushback = typeof raw.partner_pushback === "string" ? raw.partner_pushback.trim() : "";
18996
+ const scoreRaw = typeof raw.score === "number" ? raw.score : Number(raw.score);
18997
+ const score = Number.isFinite(scoreRaw) ? Math.max(0, Math.min(100, scoreRaw)) : 0;
18998
+ const gaps = [];
18999
+ if (Array.isArray(raw.gaps)) {
19000
+ for (const item of raw.gaps) {
19001
+ if (!item || typeof item !== "object") continue;
19002
+ const rec = item;
19003
+ const code = asGapCode(rec.code);
19004
+ if (!code) continue;
19005
+ gaps.push({
19006
+ code,
19007
+ severity: rec.severity === "advisory" ? "advisory" : "blocking",
19008
+ note: typeof rec.note === "string" ? rec.note : "",
19009
+ fix: typeof rec.fix === "string" ? rec.fix : ""
19010
+ });
19011
+ }
19012
+ }
19013
+ if (objectiveEcho !== opts.armedObjective) {
19014
+ gaps.push({
19015
+ code: "objective_drift",
19016
+ severity: "blocking",
19017
+ note: "Critic objective_echo does not match the armed objective.",
19018
+ fix: "Echo the armed objective exactly."
19019
+ });
19020
+ }
19021
+ if (!killed) {
19022
+ gaps.push({
19023
+ code: "alternatives_killed",
19024
+ severity: "blocking",
19025
+ note: "Critic did not name an approach that failed newness, stake, or exams.",
19026
+ fix: "Name one killed alternative so the plan is not a restatement of the status quo."
19027
+ });
19028
+ }
19029
+ for (const gap of opts.rubricBlocking) {
19030
+ if (!gaps.some((g) => g.code === gap.code)) gaps.push(gap);
19031
+ }
19032
+ const blocking = gaps.filter((g) => g.severity === "blocking");
19033
+ const modelShip = raw.ship === true;
19034
+ const ship = modelShip && blocking.length === 0 && score >= opts.minScore && objectiveEcho === opts.armedObjective && killed.length > 0;
19035
+ return {
19036
+ ship,
19037
+ score,
19038
+ objective_echo: objectiveEcho,
19039
+ gaps,
19040
+ killed_alternative: killed,
19041
+ partner_pushback: pushback
19042
+ };
19043
+ }
19044
+ function lockPlanObjective(plan, armedObjective) {
19045
+ return { ...plan, objective: armedObjective };
19046
+ }
19047
+ var OWNER_TOKENS, VITAL_ALIASES, START_SOON_RE, GAP_CODES;
19048
+ var init_strategist_rubric = __esm({
19049
+ "src/ai/strategist-rubric.ts"() {
19050
+ "use strict";
19051
+ init_playbook();
19052
+ init_strategist_validate();
19053
+ init_formatters();
19054
+ OWNER_TOKENS = [
19055
+ "revops",
19056
+ "rev ops",
19057
+ "revenue ops",
19058
+ "sales manager",
19059
+ "sales ops",
19060
+ "cs lead",
19061
+ "customer success",
19062
+ "marketing ops",
19063
+ "ae lead",
19064
+ "account executive lead",
19065
+ "sdr lead",
19066
+ "bdr lead",
19067
+ "demand gen",
19068
+ "enablement"
19069
+ ];
19070
+ VITAL_ALIASES = {
19071
+ freshness: ["freshness", "stale", "zombie", "dead pipeline"],
19072
+ flow_rate: ["flow_rate", "flow rate", "stuck", "velocity", "stage"],
19073
+ drop_rate: ["drop_rate", "drop rate", "handoff", "leak"],
19074
+ signal_to_noise: ["signal_to_noise", "signal-to-noise", "signal to noise", "noise", "misdirected"],
19075
+ thread_depth: ["thread_depth", "thread depth", "single-thread", "single threaded", "multi-thread"]
19076
+ };
19077
+ START_SOON_RE = /\b(48\s*-?hours?|48h|tomorrow|today|monday|this week|within two days|within 2 days)\b/i;
19078
+ GAP_CODES = [
19079
+ "bottleneck",
19080
+ "stake",
19081
+ "owner",
19082
+ "start_48h",
19083
+ "effort",
19084
+ "scope",
19085
+ "exams",
19086
+ "alternatives_killed",
19087
+ "thin_evidence",
19088
+ "invented_numbers",
19089
+ "objective_drift"
19090
+ ];
19091
+ }
19092
+ });
19093
+
18431
19094
  // src/ai/strategist.ts
18432
19095
  function buildHealthSnapshot(computeResult, divergences) {
18433
19096
  const { aggregate, segments } = computeResult;
@@ -18650,6 +19313,15 @@ Respond with ONLY the corrected plan JSON object in the required schema (title,
18650
19313
  for (const issue of validated.issues) {
18651
19314
  yield { type: "notice", text: issue };
18652
19315
  }
19316
+ const rubric = scoreConsultantPlan(validated.plan, {
19317
+ snapshot: options.computeResult,
19318
+ constraintsNote: options.constraintsNote,
19319
+ todayIso,
19320
+ evidenceText
19321
+ });
19322
+ for (const gap of rubric.blocking) {
19323
+ yield { type: "notice", text: `Plan gap (${gap.code}): ${gap.note}` };
19324
+ }
18653
19325
  yield {
18654
19326
  type: "plan",
18655
19327
  plan: validated.plan,
@@ -18701,6 +19373,7 @@ var init_strategist2 = __esm({
18701
19373
  init_thread();
18702
19374
  init_strategist_prompt();
18703
19375
  init_strategist_validate();
19376
+ init_strategist_rubric();
18704
19377
  init_strategist_prompt();
18705
19378
  GROUND_MAX_ROUNDS = 6;
18706
19379
  BACKCAST_MAX_ROUNDS = 4;
@@ -18715,25 +19388,673 @@ var init_strategist2 = __esm({
18715
19388
  }
18716
19389
  });
18717
19390
 
18718
- // src/output/strategy-brief.ts
18719
- import chalk16 from "chalk";
18720
- function printWrapped(text, width, prefix = INDENT, style) {
18721
- for (const line of wrapWords(text, width)) {
18722
- console.log(prefix + (style ? style(line) : line));
18723
- }
19391
+ // src/ai/strategist-craft.ts
19392
+ import { createHash as createHash3 } from "crypto";
19393
+ function snapshotHash(snapshotJson) {
19394
+ return createHash3("sha256").update(snapshotJson).digest("hex").slice(0, 16);
18724
19395
  }
18725
- function outcomeLine(outcome) {
18726
- return `${chalk16.bold(outcome.metric)}: ${outcome.baseline} ${chalk16.dim("->")} ${chalk16.bold(outcome.target_range)} ${chalk16.dim(`by ${outcome.check_date} \xB7 ${outcome.measured_by}`)}`;
19396
+ function totalTokens(job) {
19397
+ return job.cumulative_input_tokens + job.cumulative_output_tokens;
18727
19398
  }
18728
- function printWorkstream(ws, width) {
18729
- const plays = ws.play_ids.length > 0 ? chalk16.dim(` Play: ${ws.play_ids.join(", ")}`) : "";
18730
- console.log(`${INDENT}${paint("accent", `${ws.order}.`)} ${chalk16.bold(ws.title)}${plays}`);
18731
- printWrapped(ws.problem, width - 5, INDENT + " ", (s) => chalk16.dim(s));
18732
- if (ws.rationale) {
18733
- printWrapped(`Reason: ${ws.rationale}`, width - 5, INDENT + " ", (s) => chalk16.dim(s));
18734
- }
18735
- console.log(`${INDENT} ${outcomeLine(ws.expected_outcome)}`);
18736
- for (const li of ws.leading_indicators) {
19399
+ function fallbackNotice(text) {
19400
+ return /grounded (playbook )?fallback/i.test(text);
19401
+ }
19402
+ function canShip(job, critic, rubricBlocking) {
19403
+ if (!critic.ship) return false;
19404
+ if (rubricBlocking.length > 0) return false;
19405
+ if (critic.score < job.min_score) return false;
19406
+ if (job.from_fallback && !job.revised_once) return false;
19407
+ return true;
19408
+ }
19409
+ function recordScoreStreak(job, score) {
19410
+ if (job.last_critic_score != null && score <= job.last_critic_score) {
19411
+ job.no_improve_streak += 1;
19412
+ } else {
19413
+ job.no_improve_streak = 0;
19414
+ }
19415
+ job.last_critic_score = score;
19416
+ }
19417
+ async function* strategistCraftSession(options) {
19418
+ assertReplAi(options.ctx);
19419
+ const todayIso = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
19420
+ const healthSnapshot = buildHealthSnapshot(options.computeResult, options.divergences);
19421
+ const maxRounds = options.maxRounds ?? DEFAULT_CRAFT_MAX_ROUNDS;
19422
+ const minScore = options.minScore ?? DEFAULT_CRAFT_MIN_SCORE;
19423
+ const complete = options.complete ?? completeWithFailover;
19424
+ const runIteration0 = options.runIteration0 ?? strategistPlanSession;
19425
+ const job = options.job ?? createRuminationJob({
19426
+ objective: options.objective,
19427
+ constraintsNote: options.constraintsNote,
19428
+ baselineBatchId: options.baselineBatchId,
19429
+ snapshotHash: snapshotHash(healthSnapshot),
19430
+ minScore,
19431
+ maxRounds,
19432
+ maxTokens: options.maxTokens
19433
+ });
19434
+ if (!options.job) job.objective = options.objective;
19435
+ job.min_score = minScore;
19436
+ job.max_rounds = maxRounds;
19437
+ if (options.maxTokens != null) job.max_tokens = options.maxTokens;
19438
+ saveRuminationJob(job);
19439
+ options.ctx.lastCraftJobId = job.status === "ready" ? void 0 : job.id;
19440
+ saveSessionState(options.ctx);
19441
+ const evidenceText = healthSnapshot;
19442
+ const armedObjective = job.objective;
19443
+ let lastMeta = { provider_used: "unknown", model_used: "unknown" };
19444
+ let measurable = { measurable_targets: 0, total_targets: 0 };
19445
+ const scorePlan = (plan) => scoreConsultantPlan(plan, {
19446
+ snapshot: options.computeResult,
19447
+ constraintsNote: options.constraintsNote ?? job.constraints_note,
19448
+ armedObjective,
19449
+ todayIso,
19450
+ evidenceText
19451
+ });
19452
+ const rememberBest = (plan, score) => {
19453
+ if (score == null) {
19454
+ if (!job.best_plan) job.best_plan = plan;
19455
+ return;
19456
+ }
19457
+ if (job.best_score == null || score > job.best_score) {
19458
+ job.best_score = score;
19459
+ job.best_plan = plan;
19460
+ }
19461
+ };
19462
+ try {
19463
+ if (job.status === "ready" && job.plan) {
19464
+ const locked = lockPlanObjective(job.plan, armedObjective);
19465
+ job.plan = locked;
19466
+ saveRuminationJob(job);
19467
+ yield {
19468
+ type: "plan",
19469
+ plan: locked,
19470
+ issues: [],
19471
+ measurable_targets: measurable.measurable_targets,
19472
+ total_targets: measurable.total_targets,
19473
+ baseline_batch_id: options.baselineBatchId ?? null
19474
+ };
19475
+ yield {
19476
+ type: "done",
19477
+ model_used: lastMeta.model_used,
19478
+ provider_used: lastMeta.provider_used,
19479
+ usage: lastMeta
19480
+ };
19481
+ return { job, plan: locked, status: job.status, stop_reason: job.stop_reason };
19482
+ }
19483
+ if (!job.plan) {
19484
+ yield { type: "stage", stage: "ground", label: "Craft \u2014 grounding, sequencing, stress-testing" };
19485
+ for await (const event of runIteration0({ ...options, objective: armedObjective })) {
19486
+ if (options.interrupted?.()) {
19487
+ job.status = "unfinished";
19488
+ job.stop_reason = "interrupt";
19489
+ saveRuminationJob(job);
19490
+ break;
19491
+ }
19492
+ if (event.type === "notice" && fallbackNotice(event.text)) {
19493
+ job.from_fallback = true;
19494
+ }
19495
+ if (event.type === "plan") {
19496
+ const locked = lockPlanObjective(event.plan, armedObjective);
19497
+ job.plan = locked;
19498
+ rememberBest(locked, null);
19499
+ measurable = {
19500
+ measurable_targets: event.measurable_targets,
19501
+ total_targets: event.total_targets
19502
+ };
19503
+ const rubric = scorePlan(locked);
19504
+ job.rubric_gaps = rubric.gaps;
19505
+ yield { ...event, plan: locked, issues: [...event.issues, ...rubric.blocking.map((g) => `Plan gap (${g.code}): ${g.note}`)] };
19506
+ } else if (event.type === "done") {
19507
+ lastMeta = event.usage ?? lastMeta;
19508
+ job.cumulative_input_tokens += event.usage?.input_tokens ?? 0;
19509
+ job.cumulative_output_tokens += event.usage?.output_tokens ?? 0;
19510
+ yield event;
19511
+ } else {
19512
+ yield event;
19513
+ }
19514
+ }
19515
+ saveRuminationJob(job);
19516
+ }
19517
+ if (!job.plan) {
19518
+ job.status = "unfinished";
19519
+ job.stop_reason = job.stop_reason ?? "error";
19520
+ saveRuminationJob(job);
19521
+ yield {
19522
+ type: "done",
19523
+ model_used: lastMeta.model_used,
19524
+ provider_used: lastMeta.provider_used,
19525
+ usage: lastMeta
19526
+ };
19527
+ return { job, plan: null, status: job.status, stop_reason: job.stop_reason };
19528
+ }
19529
+ rememberBest(job.plan, job.best_score);
19530
+ const systemPrompt = buildStrategistSystemPrompt(todayIso);
19531
+ const callText = async (surface, userContent, maxTokens) => {
19532
+ const result = await complete(
19533
+ {
19534
+ surface,
19535
+ messages: [{ role: "user", content: userContent }],
19536
+ system: systemPrompt,
19537
+ max_tokens: maxTokens
19538
+ },
19539
+ { ctx: options.ctx }
19540
+ );
19541
+ lastMeta = result.meta;
19542
+ return {
19543
+ text: result.response.text,
19544
+ input: result.response.token_usage?.input_tokens ?? result.meta.input_tokens ?? 0,
19545
+ output: result.response.token_usage?.output_tokens ?? result.meta.output_tokens ?? 0
19546
+ };
19547
+ };
19548
+ let stop;
19549
+ const startRound = (job.iterations.at(-1)?.round ?? 0) + 1;
19550
+ for (let round = startRound; round <= maxRounds; round++) {
19551
+ if (options.interrupted?.()) {
19552
+ stop = "interrupt";
19553
+ break;
19554
+ }
19555
+ if (job.max_tokens != null && totalTokens(job) >= job.max_tokens) {
19556
+ stop = "budget_tokens";
19557
+ break;
19558
+ }
19559
+ const plan = job.plan;
19560
+ if (!plan) {
19561
+ stop = "error";
19562
+ break;
19563
+ }
19564
+ const rubric = scorePlan(plan);
19565
+ job.rubric_gaps = rubric.gaps;
19566
+ const criticRaw = await callText(
19567
+ "strategist_critic",
19568
+ buildCriticMessage({
19569
+ objective: armedObjective,
19570
+ planJson: JSON.stringify(plan),
19571
+ rubricGaps: rubric.blocking.map((g) => `${g.code}: ${g.note}`).join("\n") || "(none)",
19572
+ healthSnapshot
19573
+ }),
19574
+ CRITIC_MAX_TOKENS
19575
+ );
19576
+ const critic = parseCriticResponse(parseJsonObjectFromText(criticRaw.text), {
19577
+ armedObjective,
19578
+ minScore,
19579
+ rubricBlocking: rubric.blocking
19580
+ });
19581
+ job.last_critic = critic;
19582
+ recordScoreStreak(job, critic.score);
19583
+ rememberBest(plan, critic.score);
19584
+ const tokensThisRound = criticRaw.input + criticRaw.output;
19585
+ let decision = "revise";
19586
+ if (canShip(job, critic, rubric.blocking)) {
19587
+ decision = "ship";
19588
+ } else if (job.no_improve_streak >= 2) {
19589
+ decision = "no-delta";
19590
+ }
19591
+ addRuminationIteration(job, {
19592
+ round,
19593
+ critic_score: critic.score,
19594
+ ship: critic.ship,
19595
+ blocking_codes: critic.gaps.filter((g) => g.severity === "blocking").map((g) => g.code),
19596
+ tokens_in: criticRaw.input,
19597
+ tokens_out: criticRaw.output,
19598
+ decision
19599
+ });
19600
+ yield {
19601
+ type: "craft_round",
19602
+ round,
19603
+ max: maxRounds,
19604
+ score: critic.score,
19605
+ tokensThisRound,
19606
+ tokensTotal: totalTokens(job),
19607
+ decision
19608
+ };
19609
+ yield {
19610
+ type: "notice",
19611
+ text: `craft ${round}/${maxRounds} \xB7 score ${critic.score} \xB7 ${tokensThisRound} tok this round \xB7 ${totalTokens(job)} total`
19612
+ };
19613
+ saveRuminationJob(job);
19614
+ if (decision === "ship") {
19615
+ stop = "ship";
19616
+ break;
19617
+ }
19618
+ if (decision === "no-delta") {
19619
+ stop = "no_delta";
19620
+ break;
19621
+ }
19622
+ const needsGround = critic.gaps.some((g) => g.code === "thin_evidence") && !job.extra_ground_used;
19623
+ if (needsGround) {
19624
+ job.extra_ground_used = true;
19625
+ yield { type: "notice", text: "Critic asked for thinner evidence \u2014 one extra ground pass." };
19626
+ for await (const event of runIteration0({ ...options, objective: armedObjective })) {
19627
+ if (event.type === "notice" && fallbackNotice(event.text)) {
19628
+ job.from_fallback = true;
19629
+ } else if (event.type === "plan") {
19630
+ job.plan = lockPlanObjective(event.plan, armedObjective);
19631
+ rememberBest(job.plan, null);
19632
+ measurable = {
19633
+ measurable_targets: event.measurable_targets,
19634
+ total_targets: event.total_targets
19635
+ };
19636
+ } else if (event.type !== "done") {
19637
+ yield event;
19638
+ }
19639
+ }
19640
+ saveRuminationJob(job);
19641
+ continue;
19642
+ }
19643
+ if (options.interrupted?.()) {
19644
+ stop = "interrupt";
19645
+ break;
19646
+ }
19647
+ if (job.max_tokens != null && totalTokens(job) >= job.max_tokens) {
19648
+ stop = "budget_tokens";
19649
+ break;
19650
+ }
19651
+ const reviseRaw = await callText(
19652
+ "strategist",
19653
+ buildReviseMessage({
19654
+ objective: armedObjective,
19655
+ planJson: JSON.stringify(plan),
19656
+ gapsJson: JSON.stringify(critic.gaps)
19657
+ }),
19658
+ REVISE_MAX_TOKENS
19659
+ );
19660
+ job.cumulative_input_tokens += reviseRaw.input;
19661
+ job.cumulative_output_tokens += reviseRaw.output;
19662
+ const parsed = parseJsonObjectFromText(reviseRaw.text);
19663
+ const validated = parsed ? validateStrategistPlan(parsed, { evidenceText, todayIso }) : null;
19664
+ if (validated) {
19665
+ job.plan = lockPlanObjective(validated.plan, armedObjective);
19666
+ job.revised_once = true;
19667
+ job.from_fallback = false;
19668
+ measurable = {
19669
+ measurable_targets: validated.measurableTargets,
19670
+ total_targets: validated.totalTargets
19671
+ };
19672
+ const nextRubric = scorePlan(job.plan);
19673
+ job.rubric_gaps = nextRubric.gaps;
19674
+ } else {
19675
+ yield { type: "notice", text: "Revise JSON invalid \u2014 keeping prior plan." };
19676
+ }
19677
+ saveRuminationJob(job);
19678
+ }
19679
+ if (!stop) stop = "budget_rounds";
19680
+ if (stop !== "ship" && job.best_plan) {
19681
+ job.plan = lockPlanObjective(job.best_plan, armedObjective);
19682
+ }
19683
+ job.stop_reason = stop;
19684
+ job.status = stop === "ship" ? "ready" : "unfinished";
19685
+ saveRuminationJob(job);
19686
+ if (job.plan) {
19687
+ const rubric = scorePlan(job.plan);
19688
+ yield {
19689
+ type: "plan",
19690
+ plan: job.plan,
19691
+ issues: rubric.blocking.map((g) => `Plan gap (${g.code}): ${g.note}`),
19692
+ measurable_targets: measurable.measurable_targets,
19693
+ total_targets: measurable.total_targets,
19694
+ baseline_batch_id: options.baselineBatchId ?? null
19695
+ };
19696
+ }
19697
+ yield {
19698
+ type: "done",
19699
+ model_used: lastMeta.model_used,
19700
+ provider_used: lastMeta.provider_used,
19701
+ failover: lastMeta.failover,
19702
+ usage: {
19703
+ ...lastMeta,
19704
+ input_tokens: job.cumulative_input_tokens,
19705
+ output_tokens: job.cumulative_output_tokens
19706
+ }
19707
+ };
19708
+ return { job, plan: job.plan, status: job.status, stop_reason: job.stop_reason };
19709
+ } catch (err) {
19710
+ job.status = "unfinished";
19711
+ job.stop_reason = "error";
19712
+ if (job.best_plan) job.plan = lockPlanObjective(job.best_plan, armedObjective);
19713
+ saveRuminationJob(job);
19714
+ throw err;
19715
+ }
19716
+ }
19717
+ function formatCraftRoundLine(event) {
19718
+ const score = event.score == null ? "\u2014" : String(event.score);
19719
+ const tok = event.tokensThisRound >= 1e3 ? `${Math.round(event.tokensThisRound / 1e3)}k` : String(event.tokensThisRound);
19720
+ const total = event.tokensTotal >= 1e3 ? `${Math.round(event.tokensTotal / 1e3)}k` : String(event.tokensTotal);
19721
+ return `craft ${event.round}/${event.max} \xB7 score ${score} \xB7 ${tok} tok this round \xB7 ${total} total`;
19722
+ }
19723
+ var CRITIC_MAX_TOKENS, REVISE_MAX_TOKENS, DEFAULT_CRAFT_MAX_ROUNDS, DEFAULT_CRAFT_MIN_SCORE;
19724
+ var init_strategist_craft = __esm({
19725
+ "src/ai/strategist-craft.ts"() {
19726
+ "use strict";
19727
+ init_failover();
19728
+ init_repl_api();
19729
+ init_context2();
19730
+ init_strategist2();
19731
+ init_strategist_validate();
19732
+ init_strategist_rubric();
19733
+ init_strategist_prompt();
19734
+ init_store3();
19735
+ CRITIC_MAX_TOKENS = 2048;
19736
+ REVISE_MAX_TOKENS = 8192;
19737
+ DEFAULT_CRAFT_MAX_ROUNDS = 5;
19738
+ DEFAULT_CRAFT_MIN_SCORE = 85;
19739
+ }
19740
+ });
19741
+
19742
+ // src/ruminations/handoff.ts
19743
+ function renderCraftHandoffMarkdown(opts) {
19744
+ const { plan } = opts;
19745
+ const lines = [];
19746
+ lines.push("# Action plan \u2014 NTRP craft");
19747
+ lines.push("");
19748
+ lines.push("Use this file as a prompt for another agent or as a morning brief.");
19749
+ lines.push("");
19750
+ lines.push("## The Call");
19751
+ lines.push("");
19752
+ lines.push(plan.summary_30k);
19753
+ lines.push("");
19754
+ lines.push("## Locked objective");
19755
+ lines.push("");
19756
+ lines.push(plan.objective);
19757
+ lines.push("");
19758
+ lines.push(renderConstraintHeading(opts.constraintLine).trimEnd());
19759
+ lines.push("");
19760
+ lines.push(renderScopeHeading(plan.constraints, opts.outOfScope).trimEnd());
19761
+ lines.push("");
19762
+ lines.push("## Hypothesis");
19763
+ lines.push("");
19764
+ lines.push(plan.hypothesis);
19765
+ lines.push("");
19766
+ lines.push(renderKilledAlternativeLine(opts.killedAlternative));
19767
+ lines.push("");
19768
+ lines.push(renderEffortHeading(plan.workstreams).trimEnd());
19769
+ lines.push("");
19770
+ lines.push("## Workstreams");
19771
+ lines.push("");
19772
+ for (const ws of plan.workstreams) {
19773
+ lines.push(`### ${ws.order}. ${ws.title}`);
19774
+ lines.push("");
19775
+ lines.push(`- Problem: ${ws.problem}`);
19776
+ lines.push(`- Why this order: ${ws.rationale}`);
19777
+ if (ws.actions[0]) lines.push(`- First action (48h): ${ws.actions[0]}`);
19778
+ lines.push(`- Effort: ~${Math.round(ws.effort_hours)} team-hours`);
19779
+ lines.push(
19780
+ `- Exam: ${ws.expected_outcome.metric} ${ws.expected_outcome.baseline} -> ${ws.expected_outcome.target_range} by ${ws.expected_outcome.check_date} (${ws.expected_outcome.measured_by})`
19781
+ );
19782
+ lines.push(
19783
+ `- Contingency: if ${ws.contingency.trigger} (check ${ws.contingency.trigger_check_date}) -> ${ws.contingency.fallback}`
19784
+ );
19785
+ lines.push("");
19786
+ }
19787
+ if (plan.risks.length > 0) {
19788
+ lines.push("## Risks");
19789
+ lines.push("");
19790
+ for (const r of plan.risks) lines.push(`- ${r}`);
19791
+ lines.push("");
19792
+ }
19793
+ if (plan.assumptions.length > 0) {
19794
+ lines.push("## Assumptions");
19795
+ lines.push("");
19796
+ for (const a of plan.assumptions) lines.push(`- ${a}`);
19797
+ lines.push("");
19798
+ }
19799
+ lines.push(renderReviewHeading({ cadence: plan.review_cadence, slug: opts.slug }).trimEnd());
19800
+ lines.push("");
19801
+ if (opts.journalPath) {
19802
+ lines.push("## Craft log");
19803
+ lines.push("");
19804
+ lines.push(`Status: ${opts.status}`);
19805
+ lines.push(`Log: ${opts.journalPath}`);
19806
+ if (opts.libraryPath) lines.push(`Strategy library: ${opts.libraryPath}`);
19807
+ lines.push("");
19808
+ } else if (opts.libraryPath) {
19809
+ lines.push("## Strategy library");
19810
+ lines.push("");
19811
+ lines.push(opts.libraryPath);
19812
+ lines.push("");
19813
+ }
19814
+ return lines.join("\n");
19815
+ }
19816
+ function writeCraftPlanHandoff(opts) {
19817
+ const journalPath = opts.jobId ? ruminationLogPath(opts.jobId) : void 0;
19818
+ const markdown = renderCraftHandoffMarkdown({
19819
+ plan: opts.plan,
19820
+ journalPath,
19821
+ libraryPath: opts.libraryPath,
19822
+ status: opts.status,
19823
+ constraintLine: opts.constraintLine,
19824
+ killedAlternative: opts.killedAlternative,
19825
+ outOfScope: opts.outOfScope,
19826
+ slug: opts.slug
19827
+ });
19828
+ const path = resolveArchivePath("prompt:plan", `handoff-plan-${exportStamp()}.md`);
19829
+ writeRedactedText(path, markdown);
19830
+ const event = recordExportWrite({
19831
+ kind: "prompt:plan",
19832
+ path,
19833
+ sessionId: opts.sessionId,
19834
+ title: `${opts.plan.title} craft plan`
19835
+ });
19836
+ return { path, inboxPath: event.inbox_path };
19837
+ }
19838
+ var init_handoff = __esm({
19839
+ "src/ruminations/handoff.ts"() {
19840
+ "use strict";
19841
+ init_exports_registry();
19842
+ init_redact_write();
19843
+ init_library();
19844
+ init_store3();
19845
+ }
19846
+ });
19847
+
19848
+ // src/services/strategist-run.ts
19849
+ async function assertComputableData() {
19850
+ await initSchema();
19851
+ let counts = {};
19852
+ try {
19853
+ counts = await getEntityCounts();
19854
+ } catch {
19855
+ counts = {};
19856
+ }
19857
+ if (!Object.values(counts).some((n) => (n ?? 0) > 0)) {
19858
+ throw new NtrpError(
19859
+ "strategy_no_data",
19860
+ "No pipeline data loaded. Ingest a CSV, run demo data, then craft a plan.",
19861
+ 4 /* NoData */
19862
+ );
19863
+ }
19864
+ }
19865
+ function rememberCraftJob(ctx, job) {
19866
+ if (!job) return;
19867
+ ctx.lastCraftJobId = job.status === "ready" ? void 0 : job.id;
19868
+ saveSessionState(ctx);
19869
+ }
19870
+ async function executeStrategistJob(req) {
19871
+ if (!canUseReplAi(req.ctx)) {
19872
+ throw new NtrpError(
19873
+ "strategy_no_key",
19874
+ "No LLM API key configured. Run /connect and paste a key, then retry craft.",
19875
+ 3 /* Auth */
19876
+ );
19877
+ }
19878
+ const resumeJob = req.resumeId ? loadRuminationJob(req.resumeId) : null;
19879
+ if (req.resumeId && !resumeJob) {
19880
+ throw new NtrpError(
19881
+ "strategy_resume_missing",
19882
+ `No craft job "${req.resumeId}". Check ~/.ntrp/ruminations/.`,
19883
+ 2 /* Usage */
19884
+ );
19885
+ }
19886
+ if (resumeJob) rememberCraftJob(req.ctx, resumeJob);
19887
+ const objective = resumeJob?.objective || req.objective;
19888
+ if (!objective.trim()) {
19889
+ throw new NtrpError(
19890
+ "strategy_objective_required",
19891
+ 'strategy craft requires an objective. Example: ntrp strategy craft "cut stale pipeline before Q4"',
19892
+ 2 /* Usage */
19893
+ );
19894
+ }
19895
+ await assertComputableData();
19896
+ const inputs = await prepareStrategistInputs(req.ctx, objective);
19897
+ const notices = [];
19898
+ let plan = resumeJob?.plan ?? null;
19899
+ let stats = { measurable_targets: 0, total_targets: 0 };
19900
+ let meta = {};
19901
+ let job = resumeJob ?? void 0;
19902
+ const sessionOpts = {
19903
+ objective,
19904
+ computeResult: inputs.snapshot,
19905
+ divergences: inputs.divergences,
19906
+ includeMetrics: inputs.includeMetrics,
19907
+ memoryBlock: inputs.memoryBlock,
19908
+ gapAuditBlock: inputs.gapAuditBlock,
19909
+ constraintsNote: req.constraintsNote ?? resumeJob?.constraints_note,
19910
+ baselineBatchId: inputs.baselineBatchId,
19911
+ ctx: req.ctx
19912
+ };
19913
+ const useCraft = req.mode === "craft" && req.untilReady !== false;
19914
+ const handleEvent = (event) => {
19915
+ req.onEvent?.(event);
19916
+ if (event.type === "notice") notices.push(event.text);
19917
+ if (event.type === "plan") {
19918
+ plan = event.plan;
19919
+ stats = {
19920
+ measurable_targets: event.measurable_targets,
19921
+ total_targets: event.total_targets
19922
+ };
19923
+ }
19924
+ if (event.type === "done") {
19925
+ meta = event.usage ?? { model_used: event.model_used, provider_used: event.provider_used };
19926
+ }
19927
+ if (event.type === "craft_round") {
19928
+ notices.push(formatCraftRoundLine(event));
19929
+ }
19930
+ };
19931
+ if (useCraft) {
19932
+ const gen = strategistCraftSession({
19933
+ ...sessionOpts,
19934
+ maxRounds: req.maxRounds ?? resumeJob?.max_rounds ?? DEFAULT_CRAFT_MAX_ROUNDS,
19935
+ maxTokens: req.maxTokens ?? resumeJob?.max_tokens,
19936
+ minScore: req.minScore ?? resumeJob?.min_score ?? DEFAULT_CRAFT_MIN_SCORE,
19937
+ job: resumeJob ?? void 0,
19938
+ interrupted: req.interrupted
19939
+ });
19940
+ let next = await gen.next();
19941
+ while (!next.done) {
19942
+ handleEvent(next.value);
19943
+ next = await gen.next();
19944
+ }
19945
+ job = next.value.job;
19946
+ plan = next.value.plan;
19947
+ } else {
19948
+ for await (const event of strategistPlanSession(sessionOpts)) {
19949
+ handleEvent(event);
19950
+ }
19951
+ }
19952
+ const blocking = job?.rubric_gaps.filter((g) => g.severity === "blocking").map((g) => g.code) ?? [];
19953
+ const status = job?.status === "unfinished" || !plan ? "unfinished" : "ready";
19954
+ const result = {
19955
+ plan,
19956
+ status,
19957
+ stop_reason: job?.stop_reason,
19958
+ objective,
19959
+ critic_score: job?.last_critic?.score ?? null,
19960
+ rounds: job?.iterations.length ?? 0,
19961
+ usage: meta,
19962
+ journal_path: job ? ruminationLogPath(job.id) : void 0,
19963
+ blocking_gaps: blocking,
19964
+ measurable_targets: stats.measurable_targets,
19965
+ total_targets: stats.total_targets,
19966
+ notices,
19967
+ job
19968
+ };
19969
+ const constraintLine = formatConstraintLine(inputs.snapshot.aggregate);
19970
+ const killedAlternative = job?.last_critic?.killed_alternative;
19971
+ if (plan && req.save) {
19972
+ const persisted = await persistStrategistPlan(plan, {
19973
+ baselineBatchId: inputs.baselineBatchId,
19974
+ craftLogPath: job ? ruminationLogPath(job.id) : void 0,
19975
+ constraintLine,
19976
+ killedAlternative
19977
+ });
19978
+ result.slug = persisted.strategy.slug;
19979
+ result.library_path = persisted.library_path;
19980
+ req.ctx.deliverables.push({
19981
+ kind: "strategy",
19982
+ at: (/* @__PURE__ */ new Date()).toISOString(),
19983
+ path: persisted.library_path,
19984
+ note: plan.title
19985
+ });
19986
+ if (job) {
19987
+ job.library_path = persisted.library_path;
19988
+ }
19989
+ }
19990
+ if (plan && req.handoff) {
19991
+ const written = writeCraftPlanHandoff({
19992
+ plan,
19993
+ jobId: job?.id,
19994
+ status: result.status,
19995
+ libraryPath: result.library_path,
19996
+ sessionId: req.ctx.sessionId,
19997
+ constraintLine,
19998
+ killedAlternative,
19999
+ slug: result.slug
20000
+ });
20001
+ result.handoff_path = written.path;
20002
+ result.inbox_path = written.inboxPath;
20003
+ if (job) {
20004
+ job.handoff_path = written.path;
20005
+ job.inbox_path = written.inboxPath;
20006
+ }
20007
+ req.ctx.deliverables.push({
20008
+ kind: "prompt:plan",
20009
+ at: (/* @__PURE__ */ new Date()).toISOString(),
20010
+ path: written.path,
20011
+ note: plan.title
20012
+ });
20013
+ }
20014
+ if (job) {
20015
+ if (req.ctx.strategistState) req.ctx.strategistState.ruminationId = job.id;
20016
+ saveRuminationJob(job);
20017
+ rememberCraftJob(req.ctx, job);
20018
+ }
20019
+ return result;
20020
+ }
20021
+ var init_strategist_run = __esm({
20022
+ "src/services/strategist-run.ts"() {
20023
+ "use strict";
20024
+ init_context2();
20025
+ init_errors2();
20026
+ init_types2();
20027
+ init_repl_api();
20028
+ init_strategist2();
20029
+ init_strategist_craft();
20030
+ init_formatters();
20031
+ init_strategist();
20032
+ init_queries();
20033
+ init_schema();
20034
+ init_store3();
20035
+ init_handoff();
20036
+ }
20037
+ });
20038
+
20039
+ // src/output/strategy-brief.ts
20040
+ import chalk16 from "chalk";
20041
+ function printWrapped(text, width, prefix = INDENT, style) {
20042
+ for (const line of wrapWords(text, width)) {
20043
+ console.log(prefix + (style ? style(line) : line));
20044
+ }
20045
+ }
20046
+ function outcomeLine(outcome) {
20047
+ return `${chalk16.bold(outcome.metric)}: ${outcome.baseline} ${chalk16.dim("->")} ${chalk16.bold(outcome.target_range)} ${chalk16.dim(`by ${outcome.check_date} \xB7 ${outcome.measured_by}`)}`;
20048
+ }
20049
+ function printWorkstream(ws, width) {
20050
+ const plays = ws.play_ids.length > 0 ? chalk16.dim(` Play: ${ws.play_ids.join(", ")}`) : "";
20051
+ console.log(`${INDENT}${paint("accent", `${ws.order}.`)} ${chalk16.bold(ws.title)}${plays}`);
20052
+ printWrapped(ws.problem, width - 5, INDENT + " ", (s) => chalk16.dim(s));
20053
+ if (ws.rationale) {
20054
+ printWrapped(`Reason: ${ws.rationale}`, width - 5, INDENT + " ", (s) => chalk16.dim(s));
20055
+ }
20056
+ console.log(`${INDENT} ${outcomeLine(ws.expected_outcome)}`);
20057
+ for (const li of ws.leading_indicators) {
18737
20058
  console.log(`${INDENT} ${chalk16.dim("Lead:")} ${outcomeLine(li)}`);
18738
20059
  }
18739
20060
  if (ws.milestones.length > 0) {
@@ -18806,6 +20127,40 @@ function printStrategyBrief(plan, stats) {
18806
20127
  console.log(`${INDENT}${coverageStyled}${chalk16.dim(` \xB7 ~${Math.round(totalHours)} total team hours across ${plan.workstreams.length} workstream${plan.workstreams.length === 1 ? "" : "s"}`)}`);
18807
20128
  console.log();
18808
20129
  }
20130
+ function printCraftWrapUp(opts) {
20131
+ console.log();
20132
+ if (opts.status === "ready") {
20133
+ console.log(INDENT + paint("accent", "Plan ready"));
20134
+ if (opts.library_path) console.log(INDENT + chalk16.dim(opts.library_path));
20135
+ if (opts.slug) {
20136
+ console.log(
20137
+ INDENT + chalk16.dim("Type ") + paint("accent", `/strategy review ${opts.slug}`) + chalk16.dim(" to check progress.")
20138
+ );
20139
+ }
20140
+ const handoff = opts.inbox_path ?? opts.handoff_path;
20141
+ if (handoff) console.log(INDENT + chalk16.dim(handoff));
20142
+ } else {
20143
+ console.log(INDENT + paint("accent", "Best so far."));
20144
+ const path = opts.library_path ?? opts.handoff_path ?? opts.inbox_path;
20145
+ if (path) console.log(INDENT + chalk16.dim(path));
20146
+ if (opts.oneShot && opts.jobId) {
20147
+ console.log(
20148
+ INDENT + chalk16.dim("Resume: ") + paint("accent", `ntrp strategy craft --resume ${opts.jobId}`)
20149
+ );
20150
+ } else {
20151
+ console.log(
20152
+ INDENT + chalk16.dim("Type ") + chalk16.cyan("keep going") + chalk16.dim(" to continue.")
20153
+ );
20154
+ }
20155
+ }
20156
+ if (opts.oneShot && opts.journal_path) {
20157
+ console.log(INDENT + chalk16.dim(`Craft log: ${opts.journal_path}`));
20158
+ }
20159
+ console.log();
20160
+ }
20161
+ function isCraftRoundNotice(text) {
20162
+ return /^craft \d+\/\d+/.test(text);
20163
+ }
18809
20164
  var INDENT;
18810
20165
  var init_strategy_brief = __esm({
18811
20166
  "src/output/strategy-brief.ts"() {
@@ -18820,7 +20175,9 @@ var init_strategy_brief = __esm({
18820
20175
  var strategist_flow_exports = {};
18821
20176
  __export(strategist_flow_exports, {
18822
20177
  extractObjectiveSeed: () => extractObjectiveSeed,
20178
+ handleKeepGoingLine: () => handleKeepGoingLine,
18823
20179
  handleStrategizeFlow: () => handleStrategizeFlow,
20180
+ isKeepGoingIntent: () => isKeepGoingIntent,
18824
20181
  isStrategistIntent: () => isStrategistIntent,
18825
20182
  promptQueuedAiStrategist: () => promptQueuedAiStrategist,
18826
20183
  queueStrategistForAnalysis: () => queueStrategistForAnalysis,
@@ -18843,7 +20200,8 @@ function queueStrategistForAnalysis(ctx, opts) {
18843
20200
  ctx.strategistState = {
18844
20201
  step: "awaiting_analysis",
18845
20202
  objective: opts.seed,
18846
- origin: opts.origin
20203
+ origin: opts.origin,
20204
+ mode: opts.mode
18847
20205
  };
18848
20206
  saveSessionState(ctx);
18849
20207
  console.log();
@@ -18868,7 +20226,7 @@ async function startStrategistFlow(ctx, opts) {
18868
20226
  objective = (snapshot ? proposeObjectiveFromSnapshot(snapshot) : null) ?? "";
18869
20227
  }
18870
20228
  if (!objective) {
18871
- ctx.strategistState = { step: "objective_input", origin: opts.origin };
20229
+ ctx.strategistState = { step: "objective_input", origin: opts.origin, mode: opts.mode };
18872
20230
  saveSessionState(ctx);
18873
20231
  console.log();
18874
20232
  console.log(" " + chalk17.dim('What is the objective? State a finish line. Example: "cut stale pipeline in half before Q4".'));
@@ -18876,7 +20234,7 @@ async function startStrategistFlow(ctx, opts) {
18876
20234
  recordMessage(ctx, "agent", "Strategist: asked for objective");
18877
20235
  return "Awaiting objective";
18878
20236
  }
18879
- ctx.strategistState = { step: "objective_confirm", objective, origin: opts.origin };
20237
+ ctx.strategistState = { step: "objective_confirm", objective, origin: opts.origin, mode: opts.mode };
18880
20238
  saveSessionState(ctx);
18881
20239
  printObjectiveCard(ctx, objective, !opts.seed);
18882
20240
  recordMessage(ctx, "agent", `Strategist objective proposed: ${objective}`);
@@ -18889,7 +20247,8 @@ async function resumeStrategistAfterCompute(ctx) {
18889
20247
  console.log(" " + paint("accent", "Analysis is ready. The strategy session continues."));
18890
20248
  await startStrategistFlow(ctx, {
18891
20249
  seed: state2.objective,
18892
- origin: state2.origin ?? "nl"
20250
+ origin: state2.origin ?? "nl",
20251
+ mode: state2.mode
18893
20252
  });
18894
20253
  }
18895
20254
  function promptQueuedAiStrategist(ctx) {
@@ -18899,6 +20258,64 @@ function promptQueuedAiStrategist(ctx) {
18899
20258
  }
18900
20259
  printObjectiveCard(ctx, state2.objective, true);
18901
20260
  }
20261
+ function isKeepGoingIntent(input) {
20262
+ return CRAFT_RE.test(input.trim());
20263
+ }
20264
+ async function handleKeepGoingLine(ctx, input) {
20265
+ const line = input.trim();
20266
+ recordMessage(ctx, "user", line);
20267
+ const job = unfinishedCraftJob(ctx.lastCraftJobId);
20268
+ if (!job) {
20269
+ console.log();
20270
+ console.log(
20271
+ " " + chalk17.dim("No plan in progress. Type ") + chalk17.cyan("how should we fix this?") + chalk17.dim(" to start one.")
20272
+ );
20273
+ console.log();
20274
+ recordMessage(ctx, "agent", "No plan in progress");
20275
+ return "No plan in progress";
20276
+ }
20277
+ if (!canUseReplAi(ctx)) {
20278
+ console.log();
20279
+ console.log(
20280
+ " " + chalk17.dim("Type ") + chalk17.cyan("/connect") + chalk17.dim(" to connect a key. Then type keep going.")
20281
+ );
20282
+ console.log();
20283
+ recordMessage(ctx, "agent", "Keep going needs a key");
20284
+ return "Keep going needs a key";
20285
+ }
20286
+ const spinner = makeSpinner("Working the plan\u2026");
20287
+ try {
20288
+ const result = await executeStrategistJob({
20289
+ ctx,
20290
+ objective: job.objective,
20291
+ constraintsNote: job.constraints_note,
20292
+ mode: "craft",
20293
+ untilReady: true,
20294
+ save: true,
20295
+ handoff: true,
20296
+ resumeId: job.id,
20297
+ onEvent: (event) => {
20298
+ if (event.type === "thinking") {
20299
+ spinner.stop();
20300
+ console.log(" " + chalk17.dim.italic(event.text));
20301
+ spinner.start();
20302
+ spinner.text = "Working the plan\u2026";
20303
+ }
20304
+ }
20305
+ });
20306
+ spinner.stop();
20307
+ printCraftReplResult(ctx, result, job.objective);
20308
+ ctx.strategistState = void 0;
20309
+ saveSessionState(ctx);
20310
+ return result.status === "ready" ? `Strategy crafted: ${result.plan?.title}` : "Strategy craft unfinished";
20311
+ } catch (err) {
20312
+ spinner.fail("Strategy craft failed");
20313
+ console.error(" " + chalk17.red(String(err.message ?? err)));
20314
+ ctx.strategistState = void 0;
20315
+ saveSessionState(ctx);
20316
+ return;
20317
+ }
20318
+ }
18902
20319
  async function handleStrategizeFlow(input, ctx) {
18903
20320
  const state2 = ctx.strategistState;
18904
20321
  if (!state2) return;
@@ -18925,6 +20342,11 @@ async function handleStrategizeFlow(input, ctx) {
18925
20342
  printObjectiveCard(ctx, state2.objective, false);
18926
20343
  return "Objective proposed";
18927
20344
  }
20345
+ if (CRAFT_RE.test(line)) {
20346
+ state2.mode = "craft";
20347
+ saveSessionState(ctx);
20348
+ return runStrategistSession(ctx);
20349
+ }
18928
20350
  if (CONFIRM_RE.test(line)) {
18929
20351
  return runStrategistSession(ctx);
18930
20352
  }
@@ -18975,7 +20397,7 @@ async function runStrategistSession(ctx) {
18975
20397
  saveSessionState(ctx);
18976
20398
  return "Skeleton plan (awaiting connect)";
18977
20399
  }
18978
- if (ctx.rl && !state2.constraintsNote) {
20400
+ if (ctx.rl && !state2.constraintsNote && state2.mode !== "craft") {
18979
20401
  const prompts = createPromptSession(ctx.rl, ctx);
18980
20402
  try {
18981
20403
  const note = await prompts.ask(
@@ -18989,7 +20411,39 @@ async function runStrategistSession(ctx) {
18989
20411
  }
18990
20412
  }
18991
20413
  console.log();
18992
- const spinner = makeSpinner("Reading live data\u2026");
20414
+ const spinner = makeSpinner(state2.mode === "craft" ? "Working the plan\u2026" : "Reading live data\u2026");
20415
+ if (state2.mode === "craft") {
20416
+ try {
20417
+ const result = await executeStrategistJob({
20418
+ ctx,
20419
+ objective,
20420
+ constraintsNote: state2.constraintsNote,
20421
+ mode: "craft",
20422
+ untilReady: true,
20423
+ save: true,
20424
+ handoff: true,
20425
+ onEvent: (event) => {
20426
+ if (event.type === "thinking") {
20427
+ spinner.stop();
20428
+ console.log(" " + chalk17.dim.italic(event.text));
20429
+ spinner.start();
20430
+ spinner.text = "Working the plan\u2026";
20431
+ }
20432
+ }
20433
+ });
20434
+ spinner.stop();
20435
+ printCraftReplResult(ctx, result, objective);
20436
+ ctx.strategistState = void 0;
20437
+ saveSessionState(ctx);
20438
+ return result.status === "ready" ? `Strategy crafted: ${result.plan?.title}` : "Strategy craft unfinished";
20439
+ } catch (err) {
20440
+ spinner.fail("Strategy craft failed");
20441
+ console.error(" " + chalk17.red(String(err.message ?? err)));
20442
+ ctx.strategistState = void 0;
20443
+ saveSessionState(ctx);
20444
+ return;
20445
+ }
20446
+ }
18993
20447
  let plan = null;
18994
20448
  let stats = { measurable_targets: 0, total_targets: 0 };
18995
20449
  let baselineBatchId = null;
@@ -19073,7 +20527,10 @@ async function runStrategistSession(ctx) {
19073
20527
  }
19074
20528
  if (saved) {
19075
20529
  try {
19076
- const persisted = await persistStrategistPlan(plan, { baselineBatchId });
20530
+ const persisted = await persistStrategistPlan(plan, {
20531
+ baselineBatchId,
20532
+ constraintLine: formatConstraintLine(ctx.snapshot.computeResult?.aggregate)
20533
+ });
19077
20534
  ctx.deliverables.push({
19078
20535
  kind: "strategy",
19079
20536
  at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -19121,19 +20578,54 @@ async function ensureSnapshot(ctx) {
19121
20578
  return null;
19122
20579
  }
19123
20580
  }
20581
+ function printCraftReplResult(ctx, result, objective) {
20582
+ if (result.plan) {
20583
+ printStrategyBrief(result.plan, {
20584
+ measurable_targets: result.measurable_targets,
20585
+ total_targets: result.total_targets
20586
+ });
20587
+ } else {
20588
+ console.log(" " + chalk17.dim("(No plan produced)"));
20589
+ }
20590
+ for (const notice of result.notices.slice(0, 8)) {
20591
+ if (!isCraftRoundNotice(notice)) console.log(" " + chalk17.dim(notice));
20592
+ }
20593
+ printLlmAttribution(result.usage);
20594
+ if (result.library_path) creditStrategySession(ctx);
20595
+ printCraftWrapUp({
20596
+ status: result.status,
20597
+ library_path: result.library_path,
20598
+ inbox_path: result.inbox_path,
20599
+ handoff_path: result.handoff_path,
20600
+ slug: result.slug,
20601
+ jobId: result.job?.id,
20602
+ oneShot: false
20603
+ });
20604
+ if (result.library_path || result.handoff_path) {
20605
+ recordMessage(ctx, "agent", `Strategy crafted: ${result.plan?.title ?? objective} (${result.status})`);
20606
+ }
20607
+ }
19124
20608
  function printObjectiveCard(ctx, objective, proposed) {
20609
+ const craftMode = ctx.strategistState?.mode === "craft";
19125
20610
  console.log();
19126
20611
  console.log(" " + chalk17.bold("Strategy session"));
19127
20612
  console.log(
19128
20613
  " " + chalk17.dim(proposed ? "Proposed objective: " : "Objective: ") + paint("accent", objective)
19129
20614
  );
19130
20615
  console.log(
19131
- " " + chalk17.dim("NTRP uses your live data. It sequences the work and sets measured milestones. It then stress-tests the plan.")
20616
+ " " + chalk17.dim(
20617
+ craftMode ? "NTRP uses your live data. It keeps working until the plan is ready, then saves it and writes a handoff." : "NTRP uses your live data. It sequences the work and sets measured milestones. It then stress-tests the plan."
20618
+ )
19132
20619
  );
19133
20620
  console.log();
19134
20621
  console.log(
19135
20622
  " " + chalk17.dim("Confirm? ") + chalk17.cyan("\u23CE yes") + chalk17.dim(" \xB7 ") + chalk17.cyan("b back") + chalk17.dim(" \xB7 ") + chalk17.cyan("adjust") + chalk17.dim(" \xB7 ") + chalk17.cyan("cancel")
19136
20623
  );
20624
+ if (!craftMode) {
20625
+ console.log(
20626
+ " " + chalk17.dim("or type ") + chalk17.cyan("keep going") + chalk17.dim(" to keep working until the plan is ready")
20627
+ );
20628
+ }
19137
20629
  console.log();
19138
20630
  }
19139
20631
  async function printKeylessSkeletonPlan(ctx, objective) {
@@ -19195,7 +20687,7 @@ async function resumeStrategistAfterConnect(ctx) {
19195
20687
  printObjectiveCard(ctx, state2.objective, true);
19196
20688
  return true;
19197
20689
  }
19198
- var STRATEGIST_INTENT_RE, CANCEL_RE, CONFIRM_RE, ADJUST_RE, QUESTION_RE;
20690
+ var STRATEGIST_INTENT_RE, CANCEL_RE, CONFIRM_RE, ADJUST_RE, QUESTION_RE, CRAFT_RE;
19199
20691
  var init_strategist_flow = __esm({
19200
20692
  "src/conversation/strategist-flow.ts"() {
19201
20693
  "use strict";
@@ -19209,15 +20701,18 @@ var init_strategist_flow = __esm({
19209
20701
  init_divergence();
19210
20702
  init_strategist();
19211
20703
  init_strategist2();
20704
+ init_strategist_run();
19212
20705
  init_strategy_brief();
19213
20706
  init_llm_attribution();
19214
20707
  init_time_bank();
19215
20708
  init_formatters();
20709
+ init_store3();
19216
20710
  STRATEGIST_INTENT_RE = /\b(strateg(y|ize|ic)|game\s?plan|battle\s?plan|roadmap|(build|draft|make|create|put together)\s+(me\s+)?(a\s+|the\s+)?plan\b|plan\s+(to|for)\s+(fix|improv|reduc|recover|hit|reach|get|grow|turn)|how\s+(should|do|can)\s+we\s+(fix|approach|tackle|attack|prioritize|sequence|turn\s+(this|it)\s+around)|what\s+should\s+we\s+(do|fix|tackle|prioritize|focus\s+on)\s+(first|next)|what\s+order\s+should|where\s+(do|should)\s+we\s+start)\b/i;
19217
20711
  CANCEL_RE = /^(cancel|stop|quit|abort|never\s?mind|nevermind|forget it)\s*[.!]?\s*$/i;
19218
20712
  CONFIRM_RE = /^(y|yes|yep|yeah|confirm|go|go ahead|do it|proceed|sounds good|looks good|lgtm|ok|okay)\b/i;
19219
20713
  ADJUST_RE = /^(n|no|adjust|change|edit|different|not quite|refine)\b/i;
19220
20714
  QUESTION_RE = /(\?\s*$)|^(what|why|how|when|which|where|who)\b/i;
20715
+ CRAFT_RE = /^(craft|keep going|until tight|keep working)\b/i;
19221
20716
  }
19222
20717
  });
19223
20718
 
@@ -20247,8 +21742,8 @@ var init_bundle = __esm({
20247
21742
  });
20248
21743
 
20249
21744
  // src/repositories/markdown.ts
20250
- import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync15 } from "fs";
20251
- import { basename as basename5, dirname as dirname5, join as join24, resolve as resolve8 } from "path";
21745
+ import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync16 } from "fs";
21746
+ import { basename as basename5, dirname as dirname5, join as join25, resolve as resolve8 } from "path";
20252
21747
  import { stringify as stringifyYaml2 } from "yaml";
20253
21748
  function renderMarkdownFiles(pkg) {
20254
21749
  const bundleJson = JSON.stringify(pkg, null, 2) + "\n";
@@ -20471,9 +21966,9 @@ var init_markdown2 = __esm({
20471
21966
  mkdirSync13(root, { recursive: true });
20472
21967
  const written = [];
20473
21968
  for (const file of files) {
20474
- const absolutePath = join24(root, file.relativePath);
21969
+ const absolutePath = join25(root, file.relativePath);
20475
21970
  mkdirSync13(dirname5(absolutePath), { recursive: true });
20476
- writeFileSync15(absolutePath, file.contents, "utf-8");
21971
+ writeFileSync16(absolutePath, file.contents, "utf-8");
20477
21972
  written.push(absolutePath);
20478
21973
  }
20479
21974
  return {
@@ -20576,7 +22071,7 @@ var init_publish = __esm({
20576
22071
  });
20577
22072
 
20578
22073
  // src/services/smoke-protocol.ts
20579
- import { join as join25 } from "path";
22074
+ import { join as join26 } from "path";
20580
22075
  function isSmokeProtocolTrigger(input) {
20581
22076
  return normalize(input).includes(SMOKE_TRIGGER_PHRASE);
20582
22077
  }
@@ -20612,7 +22107,7 @@ async function runSmokeProtocol(_input, ctx) {
20612
22107
  });
20613
22108
  const proposalResult = await proposeRepositoryExport({
20614
22109
  target: "markdown",
20615
- directory: join25(getExportsDir(), "repository-smoke"),
22110
+ directory: join26(getExportsDir(), "repository-smoke"),
20616
22111
  source: "smoke_protocol",
20617
22112
  modelOrFixture: "smoke-protocol-v1"
20618
22113
  });
@@ -21352,7 +22847,7 @@ var init_scenario_fit = __esm({
21352
22847
  });
21353
22848
 
21354
22849
  // src/conversation/onboard-tiers.ts
21355
- import { existsSync as existsSync24, statSync as statSync4 } from "fs";
22850
+ import { existsSync as existsSync25, statSync as statSync4 } from "fs";
21356
22851
  function flagSet(tier) {
21357
22852
  return Boolean(getConfigValue(TIER_CONFIG_KEYS[tier]));
21358
22853
  }
@@ -23975,18 +25470,18 @@ var init_generator = __esm({
23975
25470
  });
23976
25471
 
23977
25472
  // src/demo/taxonomy-cache.ts
23978
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync16, existsSync as existsSync25, mkdirSync as mkdirSync14, unlinkSync as unlinkSync4 } from "fs";
25473
+ import { readFileSync as readFileSync22, writeFileSync as writeFileSync17, existsSync as existsSync26, mkdirSync as mkdirSync14, unlinkSync as unlinkSync4 } from "fs";
23979
25474
  import { homedir as homedir7 } from "os";
23980
- import { join as join26 } from "path";
25475
+ import { join as join27 } from "path";
23981
25476
  function ensureDir6() {
23982
- if (!existsSync25(NTRP_DIR4)) {
25477
+ if (!existsSync26(NTRP_DIR4)) {
23983
25478
  mkdirSync14(NTRP_DIR4, { recursive: true });
23984
25479
  }
23985
25480
  }
23986
25481
  function loadCachedTaxonomy(profile) {
23987
- if (!existsSync25(TAXONOMY_PATH)) return null;
25482
+ if (!existsSync26(TAXONOMY_PATH)) return null;
23988
25483
  try {
23989
- const parsed = JSON.parse(readFileSync21(TAXONOMY_PATH, "utf-8"));
25484
+ const parsed = JSON.parse(readFileSync22(TAXONOMY_PATH, "utf-8"));
23990
25485
  if (!parsed || typeof parsed !== "object") return null;
23991
25486
  if (parsed.profile_updated_at !== profile.updated_at) return null;
23992
25487
  return parsed;
@@ -23996,14 +25491,14 @@ function loadCachedTaxonomy(profile) {
23996
25491
  }
23997
25492
  function saveCachedTaxonomy(taxonomy) {
23998
25493
  ensureDir6();
23999
- writeFileSync16(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
25494
+ writeFileSync17(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
24000
25495
  }
24001
25496
  var NTRP_DIR4, TAXONOMY_PATH;
24002
25497
  var init_taxonomy_cache = __esm({
24003
25498
  "src/demo/taxonomy-cache.ts"() {
24004
25499
  "use strict";
24005
- NTRP_DIR4 = join26(homedir7(), ".ntrp");
24006
- TAXONOMY_PATH = join26(NTRP_DIR4, "demo-taxonomy.json");
25500
+ NTRP_DIR4 = join27(homedir7(), ".ntrp");
25501
+ TAXONOMY_PATH = join27(NTRP_DIR4, "demo-taxonomy.json");
24007
25502
  }
24008
25503
  });
24009
25504
 
@@ -24461,7 +25956,7 @@ __export(inbox_setup_exports, {
24461
25956
  shouldOfferInboxSkillSetup: () => shouldOfferInboxSkillSetup
24462
25957
  });
24463
25958
  import chalk27 from "chalk";
24464
- import { existsSync as existsSync26 } from "fs";
25959
+ import { existsSync as existsSync27 } from "fs";
24465
25960
  function markDemoOffered() {
24466
25961
  setConfigValue("ai-inbox-nudge-seen", "true");
24467
25962
  }
@@ -24493,7 +25988,7 @@ function printSkipHint(beat) {
24493
25988
  async function reuseInboxFolderIfPresent(session, beat, folderPath) {
24494
25989
  if (getAiInboxDir()) return false;
24495
25990
  const candidates = folderPath ? [folderPath] : [.../* @__PURE__ */ new Set([defaultAiInboxDir(), legacyAiInboxDir()])];
24496
- const existing = candidates.find((p) => existsSync26(p));
25991
+ const existing = candidates.find((p) => existsSync27(p));
24497
25992
  if (!existing) return false;
24498
25993
  console.log(" " + chalk27.dim("Pickup folder still on disk: ") + existing);
24499
25994
  const reuse = await session.confirm("Reuse this pickup folder?", true);
@@ -24599,7 +26094,7 @@ __export(ingest_exports, {
24599
26094
  handler: () => handler3
24600
26095
  });
24601
26096
  import chalk28 from "chalk";
24602
- import { readFileSync as readFileSync22, existsSync as existsSync27 } from "fs";
26097
+ import { readFileSync as readFileSync23, existsSync as existsSync28 } from "fs";
24603
26098
  import { basename as basename6 } from "path";
24604
26099
  async function handler3(args, ctx) {
24605
26100
  const { positional, flags } = parseArgs(args, [
@@ -24623,7 +26118,7 @@ async function handler3(args, ctx) {
24623
26118
  console.error(chalk28.dim(" /ingest --demo [--scenario <name>]"));
24624
26119
  process.exit(1);
24625
26120
  }
24626
- if (!existsSync27(file)) {
26121
+ if (!existsSync28(file)) {
24627
26122
  console.error(chalk28.red(` File not found: ${file}`));
24628
26123
  process.exit(1);
24629
26124
  }
@@ -24641,7 +26136,7 @@ async function handler3(args, ctx) {
24641
26136
  try {
24642
26137
  await initSchema();
24643
26138
  spinner.text = "Parsing CSV\u2026";
24644
- const content = readFileSync22(file, "utf-8");
26139
+ const content = readFileSync23(file, "utf-8");
24645
26140
  const { rows, headers } = parseCSV(content);
24646
26141
  if (rows.length === 0) {
24647
26142
  spinner.fail("CSV is empty");
@@ -25011,8 +26506,8 @@ __export(ingest_chat_exports, {
25011
26506
  loadDemoFromChat: () => loadDemoFromChat,
25012
26507
  looksLikeFilePath: () => looksLikeFilePath
25013
26508
  });
25014
- import { existsSync as existsSync28, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
25015
- import { basename as basename7, join as join27, resolve as resolve9 } from "path";
26509
+ import { existsSync as existsSync29, readdirSync as readdirSync6, statSync as statSync5 } from "fs";
26510
+ import { basename as basename7, join as join28, resolve as resolve9 } from "path";
25016
26511
  import { homedir as homedir8 } from "os";
25017
26512
  import chalk30 from "chalk";
25018
26513
  function extractFilePath(input) {
@@ -25034,7 +26529,7 @@ function extractFilePath(input) {
25034
26529
  if (!candidate) continue;
25035
26530
  if (!looksLikePathToken(candidate)) continue;
25036
26531
  const p = expandPath(candidate);
25037
- if (existsSync28(p)) {
26532
+ if (existsSync29(p)) {
25038
26533
  try {
25039
26534
  const st = statSync5(p);
25040
26535
  if (st.isFile() || st.isDirectory()) return p;
@@ -25063,7 +26558,7 @@ function looksLikeFilePath(input) {
25063
26558
  function listCsvsInFolder(dir) {
25064
26559
  try {
25065
26560
  if (!statSync5(dir).isDirectory()) return [];
25066
- return readdirSync6(dir).filter((name) => name.toLowerCase().endsWith(".csv")).map((name) => join27(dir, name)).sort();
26561
+ return readdirSync6(dir).filter((name) => name.toLowerCase().endsWith(".csv")).map((name) => join28(dir, name)).sort();
25067
26562
  } catch {
25068
26563
  return [];
25069
26564
  }
@@ -25145,12 +26640,12 @@ async function ingestFromChat(ctx, filePath) {
25145
26640
  }
25146
26641
  const { handler: ingest } = await Promise.resolve().then(() => (init_ingest(), ingest_exports));
25147
26642
  const { detectEntityType: detectEntityType2 } = await Promise.resolve().then(() => (init_csv_detect(), csv_detect_exports));
25148
- const { readFileSync: readFileSync24 } = await import("fs");
26643
+ const { readFileSync: readFileSync25 } = await import("fs");
25149
26644
  const { parseCSV: parseCSV2 } = await Promise.resolve().then(() => (init_csv_parse(), csv_parse_exports));
25150
26645
  const { getStoredApiKey } = await Promise.resolve().then(() => (init_repl_api(), repl_api_exports));
25151
26646
  let headerCheckFailed = false;
25152
26647
  try {
25153
- const raw = readFileSync24(filePath, "utf-8");
26648
+ const raw = readFileSync25(filePath, "utf-8");
25154
26649
  const { headers } = parseCSV2(raw);
25155
26650
  const detected = detectEntityType2(headers, "unknown");
25156
26651
  if (!detected) headerCheckFailed = true;
@@ -26063,9 +27558,9 @@ async function handleGetSessionBrief(input) {
26063
27558
  if (!target) {
26064
27559
  return { error: `No session matching "${raw}".` };
26065
27560
  }
26066
- const { existsSync: existsSync30, readFileSync: readFileSync24 } = await import("fs");
27561
+ const { existsSync: existsSync31, readFileSync: readFileSync25 } = await import("fs");
26067
27562
  const briefPath = contextDocPathForSession2(target.id);
26068
- if (!existsSync30(briefPath)) {
27563
+ if (!existsSync31(briefPath)) {
26069
27564
  return {
26070
27565
  session_id: target.id,
26071
27566
  error: "No context brief on disk for this session (created before brief storage existed).",
@@ -26076,7 +27571,7 @@ async function handleGetSessionBrief(input) {
26076
27571
  return {
26077
27572
  session_id: target.id,
26078
27573
  security_notice: UNTRUSTED_CONTENT_NOTICE,
26079
- brief: wrapUntrustedContent(readFileSync24(briefPath, "utf-8"))
27574
+ brief: wrapUntrustedContent(readFileSync25(briefPath, "utf-8"))
26080
27575
  };
26081
27576
  }
26082
27577
  function auditDenied(name, input, resultJson, start) {
@@ -27080,15 +28575,15 @@ var init_ask = __esm({
27080
28575
  });
27081
28576
 
27082
28577
  // src/services/setup.ts
27083
- import { existsSync as existsSync29, mkdirSync as mkdirSync15, readFileSync as readFileSync23, writeFileSync as writeFileSync17 } from "fs";
27084
- import { join as join28 } from "path";
28578
+ import { existsSync as existsSync30, mkdirSync as mkdirSync15, readFileSync as readFileSync24, writeFileSync as writeFileSync18 } from "fs";
28579
+ import { join as join29 } from "path";
27085
28580
  function setupCheck() {
27086
28581
  const home = ntrpHome();
27087
28582
  let writable = false;
27088
28583
  try {
27089
28584
  mkdirSync15(home, { recursive: true });
27090
- const probe = join28(home, ".write-check");
27091
- writeFileSync17(probe, "ok\n");
28585
+ const probe = join29(home, ".write-check");
28586
+ writeFileSync18(probe, "ok\n");
27092
28587
  writable = true;
27093
28588
  } catch {
27094
28589
  writable = false;