@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.
- package/dist/index.js +1853 -283
- package/dist/mcp/server.js +1565 -70
- package/package.json +2 -1
package/dist/mcp/server.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
18719
|
-
import
|
|
18720
|
-
function
|
|
18721
|
-
|
|
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
|
|
18726
|
-
return
|
|
19396
|
+
function totalTokens(job) {
|
|
19397
|
+
return job.cumulative_input_tokens + job.cumulative_output_tokens;
|
|
18727
19398
|
}
|
|
18728
|
-
function
|
|
18729
|
-
|
|
18730
|
-
|
|
18731
|
-
|
|
18732
|
-
if (
|
|
18733
|
-
|
|
18734
|
-
|
|
18735
|
-
|
|
18736
|
-
|
|
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, {
|
|
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(
|
|
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
|
|
20251
|
-
import { basename as basename5, dirname as dirname5, join as
|
|
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 =
|
|
21969
|
+
const absolutePath = join25(root, file.relativePath);
|
|
20475
21970
|
mkdirSync13(dirname5(absolutePath), { recursive: true });
|
|
20476
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
25475
|
+
import { join as join27 } from "path";
|
|
23981
25476
|
function ensureDir6() {
|
|
23982
|
-
if (!
|
|
25477
|
+
if (!existsSync26(NTRP_DIR4)) {
|
|
23983
25478
|
mkdirSync14(NTRP_DIR4, { recursive: true });
|
|
23984
25479
|
}
|
|
23985
25480
|
}
|
|
23986
25481
|
function loadCachedTaxonomy(profile) {
|
|
23987
|
-
if (!
|
|
25482
|
+
if (!existsSync26(TAXONOMY_PATH)) return null;
|
|
23988
25483
|
try {
|
|
23989
|
-
const parsed = JSON.parse(
|
|
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
|
-
|
|
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 =
|
|
24006
|
-
TAXONOMY_PATH =
|
|
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
|
|
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) =>
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
|
25015
|
-
import { basename as basename7, join as
|
|
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 (
|
|
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) =>
|
|
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:
|
|
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 =
|
|
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:
|
|
27561
|
+
const { existsSync: existsSync31, readFileSync: readFileSync25 } = await import("fs");
|
|
26067
27562
|
const briefPath = contextDocPathForSession2(target.id);
|
|
26068
|
-
if (!
|
|
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(
|
|
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
|
|
27084
|
-
import { join as
|
|
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 =
|
|
27091
|
-
|
|
28585
|
+
const probe = join29(home, ".write-check");
|
|
28586
|
+
writeFileSync18(probe, "ok\n");
|
|
27092
28587
|
writable = true;
|
|
27093
28588
|
} catch {
|
|
27094
28589
|
writable = false;
|