promptpilot 0.1.3 → 0.1.4
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/README.md +20 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +389 -30
- package/dist/cli.js.map +1 -1
- package/dist/index.js +208 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -327,13 +327,13 @@ var modeGuidance = {
|
|
|
327
327
|
clarity: "Improve clarity, remove ambiguity, and keep the request easy for a downstream model to follow.",
|
|
328
328
|
concise: "Minimize token count while preserving user intent, constraints, and expected output.",
|
|
329
329
|
detailed: "Make the request explicit and complete, including structure and success criteria.",
|
|
330
|
-
structured: "Organize the request into
|
|
330
|
+
structured: "Organize the request into sections only when that improves clarity or token efficiency.",
|
|
331
331
|
persuasive: "Refine wording so the request is compelling and likely to elicit a thoughtful response.",
|
|
332
332
|
compress: "Aggressively compress redundant wording while preserving the meaning and critical constraints.",
|
|
333
333
|
claude_cli: "Optimize specifically for Claude CLI: compact sections, direct instructions, and minimal boilerplate."
|
|
334
334
|
};
|
|
335
335
|
var presetGuidance = {
|
|
336
|
-
code: "Favor precise technical requirements, edge cases,
|
|
336
|
+
code: "Favor precise technical requirements, edge cases, expected output format, and a compact inspect-plan-act-test-reflect loop for code tasks.",
|
|
337
337
|
email: "Preserve the sender's goal, tone, and audience; aim for a realistic and usable writing request.",
|
|
338
338
|
essay: "Preserve thesis, structure, and voice guidance while making the prompt clearer.",
|
|
339
339
|
support: "Favor concise issue context, user impact, and desired resolution details.",
|
|
@@ -351,6 +351,10 @@ function getOptimizationSystemPrompt(mode, preset) {
|
|
|
351
351
|
"- Preserve critical constraints and task goals.",
|
|
352
352
|
"- Improve clarity, structure, and downstream usefulness.",
|
|
353
353
|
"- Keep the result compact when the mode requests compression.",
|
|
354
|
+
"- Do not force sections when direct phrasing is shorter and equally clear.",
|
|
355
|
+
"- Remove redundancy aggressively when the source prompt repeats the same goal multiple ways.",
|
|
356
|
+
"- For code tasks, prefer a terse agent brief over narrative prose.",
|
|
357
|
+
"- For code tasks, structure the prompt around a Karpathy-style loop: inspect, plan, act, test, reflect, repeat.",
|
|
354
358
|
`Mode guidance: ${modeGuidance[mode]}`,
|
|
355
359
|
preset ? `Preset guidance: ${presetGuidance[preset]}` : "Preset guidance: none"
|
|
356
360
|
].join("\n");
|
|
@@ -679,7 +683,11 @@ function tokenize(value) {
|
|
|
679
683
|
);
|
|
680
684
|
}
|
|
681
685
|
function extractConstraints(value) {
|
|
682
|
-
return
|
|
686
|
+
return Array.from(
|
|
687
|
+
new Set(
|
|
688
|
+
value.split(/\n+/).flatMap((line) => line.split(/(?<=[.!?])\s+/)).map((line) => line.trim().replace(/^[-*]\s*/, "")).filter((line) => line.length > 0 && line.length <= 180).filter((line) => /(must|should|avoid|do not|don't|never|exactly|at most|under|limit|max|preserve|keep)/i.test(line))
|
|
689
|
+
)
|
|
690
|
+
).slice(0, 8);
|
|
683
691
|
}
|
|
684
692
|
function extractEntities(value) {
|
|
685
693
|
return Array.from(
|
|
@@ -874,6 +882,7 @@ var PromptOptimizer = class {
|
|
|
874
882
|
);
|
|
875
883
|
let provider = input.bypassOptimization ? "heuristic" : this.config.provider ?? DEFAULT_PROVIDER;
|
|
876
884
|
let model = provider === "ollama" ? this.config.ollamaModel ?? "auto" : "heuristic";
|
|
885
|
+
let usedPreprocessedFallback = false;
|
|
877
886
|
let optimizedPrompt = originalPrompt;
|
|
878
887
|
let providerWarnings = [];
|
|
879
888
|
let providerChanges = [];
|
|
@@ -908,6 +917,11 @@ var PromptOptimizer = class {
|
|
|
908
917
|
optimizedPrompt = ollamaResult.optimizedPrompt;
|
|
909
918
|
providerWarnings = ollamaResult.warnings;
|
|
910
919
|
providerChanges = ollamaResult.changes;
|
|
920
|
+
if (ollamaResult.source === "preprocessed") {
|
|
921
|
+
provider = "heuristic";
|
|
922
|
+
model = "cheap-preprocess";
|
|
923
|
+
usedPreprocessedFallback = true;
|
|
924
|
+
}
|
|
911
925
|
} else if (provider === "ollama") {
|
|
912
926
|
provider = "heuristic";
|
|
913
927
|
model = "heuristic";
|
|
@@ -916,7 +930,7 @@ var PromptOptimizer = class {
|
|
|
916
930
|
];
|
|
917
931
|
}
|
|
918
932
|
}
|
|
919
|
-
if (provider === "heuristic") {
|
|
933
|
+
if (provider === "heuristic" && !usedPreprocessedFallback) {
|
|
920
934
|
const fallback = this.heuristicOptimize({
|
|
921
935
|
input: {
|
|
922
936
|
...input,
|
|
@@ -1048,29 +1062,88 @@ var PromptOptimizer = class {
|
|
|
1048
1062
|
});
|
|
1049
1063
|
}
|
|
1050
1064
|
async tryOllamaOptimization(options) {
|
|
1065
|
+
const preprocessedPrompt = cheapCompress(options.input.prompt);
|
|
1066
|
+
const preprocessedTokenCount = this.estimator.estimateText(preprocessedPrompt);
|
|
1067
|
+
const ultraMode = preprocessedTokenCount > 500;
|
|
1051
1068
|
try {
|
|
1052
1069
|
if (!await this.client.isAvailable()) {
|
|
1053
|
-
return
|
|
1070
|
+
return {
|
|
1071
|
+
optimizedPrompt: preprocessedPrompt,
|
|
1072
|
+
changes: ["Applied cheap local preprocessing because Ollama was unavailable."],
|
|
1073
|
+
warnings: ["Ollama was unavailable, so PromptPilot kept the cheap preprocessed prompt."],
|
|
1074
|
+
source: "preprocessed"
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
const systemPrompt = ultraMode ? `${getOptimizationSystemPrompt(options.input.mode, options.input.preset)}
|
|
1078
|
+
Mode: Ultra compression. Minimize tokens aggressively.` : getOptimizationSystemPrompt(options.input.mode, options.input.preset);
|
|
1079
|
+
const optimizationPrompt = buildOptimizationPrompt(
|
|
1080
|
+
{
|
|
1081
|
+
...options.input,
|
|
1082
|
+
prompt: preprocessedPrompt
|
|
1083
|
+
},
|
|
1084
|
+
options.relevantContext,
|
|
1085
|
+
options.extractedConstraints
|
|
1086
|
+
);
|
|
1087
|
+
const timeoutMs = options.input.timeoutMs ?? this.config.timeoutMs;
|
|
1088
|
+
let optimizedPrompt = "";
|
|
1089
|
+
let responseChanges = [];
|
|
1090
|
+
let responseWarnings = [];
|
|
1091
|
+
try {
|
|
1092
|
+
const response = await this.client.generateJson({
|
|
1093
|
+
systemPrompt,
|
|
1094
|
+
prompt: optimizationPrompt,
|
|
1095
|
+
timeoutMs,
|
|
1096
|
+
model: options.model,
|
|
1097
|
+
temperature: this.config.temperature,
|
|
1098
|
+
format: "json"
|
|
1099
|
+
});
|
|
1100
|
+
optimizedPrompt = normalizeWhitespace(response.optimizedPrompt ?? "");
|
|
1101
|
+
responseChanges = response.changes ?? [];
|
|
1102
|
+
responseWarnings = response.warnings ?? [];
|
|
1103
|
+
} catch {
|
|
1104
|
+
const raw = await this.client.generate({
|
|
1105
|
+
systemPrompt,
|
|
1106
|
+
prompt: optimizationPrompt,
|
|
1107
|
+
timeoutMs,
|
|
1108
|
+
model: options.model,
|
|
1109
|
+
temperature: this.config.temperature
|
|
1110
|
+
});
|
|
1111
|
+
optimizedPrompt = sanitizeTextOptimizationOutput(raw);
|
|
1112
|
+
responseChanges = [`Applied text-only Ollama optimization with ${options.model}.`];
|
|
1054
1113
|
}
|
|
1055
|
-
const response = await this.client.generateJson({
|
|
1056
|
-
systemPrompt: getOptimizationSystemPrompt(options.input.mode, options.input.preset),
|
|
1057
|
-
prompt: buildOptimizationPrompt(options.input, options.relevantContext, options.extractedConstraints),
|
|
1058
|
-
timeoutMs: options.input.timeoutMs ?? this.config.timeoutMs,
|
|
1059
|
-
model: options.model,
|
|
1060
|
-
temperature: this.config.temperature,
|
|
1061
|
-
format: "json"
|
|
1062
|
-
});
|
|
1063
|
-
const optimizedPrompt = normalizeWhitespace(response.optimizedPrompt ?? "");
|
|
1064
1114
|
if (!optimizedPrompt) {
|
|
1065
|
-
return
|
|
1115
|
+
return {
|
|
1116
|
+
optimizedPrompt: preprocessedPrompt,
|
|
1117
|
+
changes: ["Applied cheap local preprocessing because the model returned an empty optimization."],
|
|
1118
|
+
warnings: ["The local optimizer returned an empty result, so PromptPilot kept the preprocessed prompt."],
|
|
1119
|
+
source: "preprocessed"
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
const optimizedTokenCount = this.estimator.estimateText(optimizedPrompt);
|
|
1123
|
+
if (isCompressionSensitiveMode(options.input.mode) && optimizedTokenCount >= preprocessedTokenCount) {
|
|
1124
|
+
return {
|
|
1125
|
+
optimizedPrompt: preprocessedPrompt,
|
|
1126
|
+
changes: [
|
|
1127
|
+
...responseChanges,
|
|
1128
|
+
"Kept the cheap preprocessed prompt because the model output was not smaller."
|
|
1129
|
+
],
|
|
1130
|
+
warnings: responseWarnings,
|
|
1131
|
+
source: "preprocessed"
|
|
1132
|
+
};
|
|
1066
1133
|
}
|
|
1067
1134
|
return {
|
|
1068
1135
|
optimizedPrompt,
|
|
1069
|
-
changes:
|
|
1070
|
-
warnings:
|
|
1136
|
+
changes: responseChanges.length > 0 ? responseChanges : [`Applied Ollama optimization with ${options.model}.`],
|
|
1137
|
+
warnings: responseWarnings,
|
|
1138
|
+
source: "ollama"
|
|
1071
1139
|
};
|
|
1072
1140
|
} catch {
|
|
1073
|
-
return
|
|
1141
|
+
return {
|
|
1142
|
+
optimizedPrompt: preprocessedPrompt,
|
|
1143
|
+
changes: ["Applied cheap local preprocessing because Ollama optimization failed."],
|
|
1144
|
+
warnings: ["Ollama optimization failed, so PromptPilot kept the preprocessed prompt."],
|
|
1145
|
+
source: "preprocessed"
|
|
1146
|
+
};
|
|
1074
1147
|
}
|
|
1075
1148
|
}
|
|
1076
1149
|
async resolveOllamaModel(options) {
|
|
@@ -1373,16 +1446,14 @@ var PromptOptimizer = class {
|
|
|
1373
1446
|
}
|
|
1374
1447
|
}
|
|
1375
1448
|
heuristicOptimize(options) {
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
options.input.task ? `Task type: ${options.input.task}` : "",
|
|
1379
|
-
options.input.tone ? `Tone: ${options.input.tone}` : "",
|
|
1380
|
-
options.input.outputFormat ? `Output format: ${options.input.outputFormat}` : "",
|
|
1381
|
-
options.input.maxLength ? `Maximum length: ${options.input.maxLength}` : "",
|
|
1382
|
-
options.constraints.length ? `Critical constraints: ${options.constraints.join("; ")}` : ""
|
|
1383
|
-
].filter(Boolean);
|
|
1449
|
+
const isCodeRequest = isCodeFirstRequest(options.input);
|
|
1450
|
+
const lines = isCodeRequest ? buildCodeFirstHeuristicPrompt(options.input, options.constraints) : buildGeneralHeuristicPrompt(options.input, options.constraints);
|
|
1384
1451
|
const optimizedPrompt = lines.join("\n");
|
|
1385
|
-
const changes =
|
|
1452
|
+
const changes = isCodeRequest ? [
|
|
1453
|
+
"Compressed the prompt into a code-agent brief.",
|
|
1454
|
+
"Removed redundant narrative phrasing.",
|
|
1455
|
+
"Applied a Karpathy-style inspect-plan-act-test-reflect loop."
|
|
1456
|
+
] : ["Normalized prompt structure for downstream model consumption."];
|
|
1386
1457
|
if (options.input.mode === "compress" || options.input.mode === "concise") {
|
|
1387
1458
|
changes.push("Applied concise formatting to reduce token usage.");
|
|
1388
1459
|
}
|
|
@@ -1463,6 +1534,14 @@ ${contextBlock}`);
|
|
|
1463
1534
|
if (constraints.length > 0) {
|
|
1464
1535
|
sections.push(`Constraints:
|
|
1465
1536
|
- ${constraints.join("\n- ")}`);
|
|
1537
|
+
}
|
|
1538
|
+
if (isCodeFirstRequest(input.input)) {
|
|
1539
|
+
sections.push(`Execution loop:
|
|
1540
|
+
- Inspect the relevant files and current behavior.
|
|
1541
|
+
- Plan the smallest safe next step.
|
|
1542
|
+
- Act with minimal, reversible changes.
|
|
1543
|
+
- Test or validate the result.
|
|
1544
|
+
- Reflect on gaps or risks, then repeat.`);
|
|
1466
1545
|
}
|
|
1467
1546
|
const desiredOutput = [
|
|
1468
1547
|
input.routingDecision.selectedTarget ? `Selected target: ${formatTargetLabel(input.routingDecision.selectedTarget)}` : input.input.targetModel ? `Target model: ${input.input.targetModel}` : "Target model: claude",
|
|
@@ -1562,6 +1641,108 @@ function describeDownstreamTarget(target) {
|
|
|
1562
1641
|
function formatTargetLabel(target) {
|
|
1563
1642
|
return target.label ?? `${target.provider}:${target.model}`;
|
|
1564
1643
|
}
|
|
1644
|
+
function isCompressionSensitiveMode(mode) {
|
|
1645
|
+
return mode === "compress" || mode === "concise" || mode === "claude_cli";
|
|
1646
|
+
}
|
|
1647
|
+
function cheapCompress(text) {
|
|
1648
|
+
return normalizeWhitespace(text).replace(/\b(?:please|kindly|just)\b/gi, "").replace(/\bI\s+(?:want|need|would\s+like\s+to)\b/gi, "").replace(/\s+([,.;:!?])/g, "$1").replace(/\s{2,}/g, " ").trim();
|
|
1649
|
+
}
|
|
1650
|
+
function sanitizeTextOptimizationOutput(raw) {
|
|
1651
|
+
const normalized = normalizeWhitespace(raw);
|
|
1652
|
+
if (!normalized) {
|
|
1653
|
+
return "";
|
|
1654
|
+
}
|
|
1655
|
+
if (!containsReasoningLeak(normalized)) {
|
|
1656
|
+
return stripWrappingQuotes(normalized);
|
|
1657
|
+
}
|
|
1658
|
+
const candidates = raw.split(/\n{2,}/).map((chunk) => stripWrappingQuotes(normalizeWhitespace(chunk))).filter(Boolean).filter((chunk) => !containsReasoningLeak(chunk)).filter((chunk) => !/^(role|task|guidelines|thinking|thinking process|attempt|critique|final decision|analysis)\b/i.test(chunk)).filter((chunk) => !/^[-*]\s/.test(chunk)).filter((chunk) => !/^\d+\.\s/.test(chunk));
|
|
1659
|
+
return candidates.at(-1) ?? stripWrappingQuotes(normalized);
|
|
1660
|
+
}
|
|
1661
|
+
function containsReasoningLeak(text) {
|
|
1662
|
+
return /(thinking process|analyze the request|drafting the optimized prompt|critique \d|attempt \d|final decision)/i.test(text);
|
|
1663
|
+
}
|
|
1664
|
+
function stripWrappingQuotes(text) {
|
|
1665
|
+
return text.replace(/^["'`]+|["'`]+$/g, "").trim();
|
|
1666
|
+
}
|
|
1667
|
+
function isCodeFirstRequest(input) {
|
|
1668
|
+
if (input.task === "code" || input.preset === "code") {
|
|
1669
|
+
return true;
|
|
1670
|
+
}
|
|
1671
|
+
if ((input.targetHints ?? []).some((hint) => ["coding", "agentic", "refactor", "debugging", "tool_use", "architecture"].includes(hint))) {
|
|
1672
|
+
return true;
|
|
1673
|
+
}
|
|
1674
|
+
return /\b(code|coding|repo|repository|refactor|patch|debug|bug|ci|test|typescript|javascript|agent|tool)\b/i.test(
|
|
1675
|
+
input.prompt
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
function buildGeneralHeuristicPrompt(input, constraints) {
|
|
1679
|
+
return [
|
|
1680
|
+
`Request: ${summarizePrompt(input.prompt, 320)}`,
|
|
1681
|
+
input.task ? `Task type: ${input.task}` : "",
|
|
1682
|
+
input.tone ? `Tone: ${input.tone}` : "",
|
|
1683
|
+
input.outputFormat ? `Output format: ${input.outputFormat}` : "",
|
|
1684
|
+
input.maxLength ? `Maximum length: ${input.maxLength}` : "",
|
|
1685
|
+
constraints.length ? `Critical constraints: ${constraints.join("; ")}` : ""
|
|
1686
|
+
].filter(Boolean);
|
|
1687
|
+
}
|
|
1688
|
+
function buildCodeFirstHeuristicPrompt(input, constraints) {
|
|
1689
|
+
const deliverables = inferCodeDeliverables(input.prompt);
|
|
1690
|
+
return [
|
|
1691
|
+
`Goal: ${summarizeCodeGoal(input.prompt)}`,
|
|
1692
|
+
input.tone ? `Tone: ${input.tone}` : "",
|
|
1693
|
+
deliverables.length ? `Deliverables:
|
|
1694
|
+
- ${deliverables.join("\n- ")}` : "",
|
|
1695
|
+
constraints.length ? `Constraints: ${constraints.join("; ")}` : "",
|
|
1696
|
+
"Use a Karpathy loop: inspect, plan, act, test, reflect, repeat."
|
|
1697
|
+
].filter(Boolean);
|
|
1698
|
+
}
|
|
1699
|
+
function summarizePrompt(prompt, maxLength) {
|
|
1700
|
+
const normalized = normalizeWhitespace(prompt);
|
|
1701
|
+
if (normalized.length <= maxLength) {
|
|
1702
|
+
return normalized;
|
|
1703
|
+
}
|
|
1704
|
+
return `${normalized.slice(0, maxLength - 1).trim()}\u2026`;
|
|
1705
|
+
}
|
|
1706
|
+
function summarizeCodeGoal(prompt) {
|
|
1707
|
+
const normalized = summarizePrompt(prompt, 220);
|
|
1708
|
+
const lowered = prompt.toLowerCase();
|
|
1709
|
+
if (/auth|authentication|login|token/.test(lowered)) {
|
|
1710
|
+
return "Inspect the codebase, understand the authentication flow, and produce a safe incremental refactor plan.";
|
|
1711
|
+
}
|
|
1712
|
+
if (/ci|debug|failing|failure|test/.test(lowered)) {
|
|
1713
|
+
return "Inspect the codebase and failing signals, identify root causes, and produce a practical debugging plan.";
|
|
1714
|
+
}
|
|
1715
|
+
if (/refactor/.test(lowered)) {
|
|
1716
|
+
return "Inspect the codebase and produce a phased refactor plan with minimal-risk execution steps.";
|
|
1717
|
+
}
|
|
1718
|
+
return normalized;
|
|
1719
|
+
}
|
|
1720
|
+
function inferCodeDeliverables(prompt) {
|
|
1721
|
+
const lowered = prompt.toLowerCase();
|
|
1722
|
+
const deliverables = [];
|
|
1723
|
+
if (/inspect|codebase|repo|repository/.test(lowered)) {
|
|
1724
|
+
deliverables.push("Summarize the relevant modules, ownership boundaries, and current behavior.");
|
|
1725
|
+
}
|
|
1726
|
+
if (/shared abstraction|shared abstractions|duplicate|duplicated/.test(lowered)) {
|
|
1727
|
+
deliverables.push("Identify duplicated logic and the best shared abstractions to extract.");
|
|
1728
|
+
}
|
|
1729
|
+
if (/incremental|phase|phased|rollout|step/.test(lowered)) {
|
|
1730
|
+
deliverables.push("Propose an incremental plan with small, reversible steps.");
|
|
1731
|
+
}
|
|
1732
|
+
if (/risk|migration|compatibility|backward/.test(lowered)) {
|
|
1733
|
+
deliverables.push("Call out migration risks, compatibility concerns, and rollback points.");
|
|
1734
|
+
}
|
|
1735
|
+
if (/test|tests/.test(lowered)) {
|
|
1736
|
+
deliverables.push("List the tests or validation needed before and after each phase.");
|
|
1737
|
+
}
|
|
1738
|
+
if (/avoid hand-wavy|practical|concrete/.test(lowered)) {
|
|
1739
|
+
deliverables.push("Keep the recommendations concrete, implementation-oriented, and free of vague architecture advice.");
|
|
1740
|
+
}
|
|
1741
|
+
if (deliverables.length === 0) {
|
|
1742
|
+
deliverables.push("Produce a compact, execution-ready plan for the coding task.");
|
|
1743
|
+
}
|
|
1744
|
+
return deliverables.slice(0, 6);
|
|
1745
|
+
}
|
|
1565
1746
|
|
|
1566
1747
|
// src/index.ts
|
|
1567
1748
|
function createOptimizer(config = {}) {
|