@oscharko-dev/keiko-model-gateway 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/capabilities.d.ts +26 -0
  3. package/dist/capabilities.d.ts.map +1 -0
  4. package/dist/capabilities.data.d.ts +3 -0
  5. package/dist/capabilities.data.d.ts.map +1 -0
  6. package/dist/capabilities.data.js +5 -0
  7. package/dist/capabilities.js +169 -0
  8. package/dist/config.d.ts +34 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +733 -0
  11. package/dist/embedding.d.ts +38 -0
  12. package/dist/embedding.d.ts.map +1 -0
  13. package/dist/embedding.js +118 -0
  14. package/dist/gateway.d.ts +23 -0
  15. package/dist/gateway.d.ts.map +1 -0
  16. package/dist/gateway.js +144 -0
  17. package/dist/http.d.ts +24 -0
  18. package/dist/http.d.ts.map +1 -0
  19. package/dist/http.js +666 -0
  20. package/dist/index.d.ts +16 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +34 -0
  23. package/dist/model-selection.d.ts +22 -0
  24. package/dist/model-selection.d.ts.map +1 -0
  25. package/dist/model-selection.js +59 -0
  26. package/dist/normalize.d.ts +9 -0
  27. package/dist/normalize.d.ts.map +1 -0
  28. package/dist/normalize.js +114 -0
  29. package/dist/openai-adapter.d.ts +22 -0
  30. package/dist/openai-adapter.d.ts.map +1 -0
  31. package/dist/openai-adapter.js +382 -0
  32. package/dist/openai-embedding-adapter.d.ts +46 -0
  33. package/dist/openai-embedding-adapter.d.ts.map +1 -0
  34. package/dist/openai-embedding-adapter.js +271 -0
  35. package/dist/promptEnhancer/__tests__/_support.d.ts +15 -0
  36. package/dist/promptEnhancer/__tests__/_support.d.ts.map +1 -0
  37. package/dist/promptEnhancer/__tests__/_support.js +28 -0
  38. package/dist/promptEnhancer/__tests__/fixtures.d.ts +8 -0
  39. package/dist/promptEnhancer/__tests__/fixtures.d.ts.map +1 -0
  40. package/dist/promptEnhancer/__tests__/fixtures.js +58 -0
  41. package/dist/promptEnhancer/__tests__/grounding-fixtures.d.ts +11 -0
  42. package/dist/promptEnhancer/__tests__/grounding-fixtures.d.ts.map +1 -0
  43. package/dist/promptEnhancer/__tests__/grounding-fixtures.js +84 -0
  44. package/dist/promptEnhancer/candidates.d.ts +32 -0
  45. package/dist/promptEnhancer/candidates.d.ts.map +1 -0
  46. package/dist/promptEnhancer/candidates.js +109 -0
  47. package/dist/promptEnhancer/critic.d.ts +22 -0
  48. package/dist/promptEnhancer/critic.d.ts.map +1 -0
  49. package/dist/promptEnhancer/critic.js +237 -0
  50. package/dist/promptEnhancer/generator.d.ts +15 -0
  51. package/dist/promptEnhancer/generator.d.ts.map +1 -0
  52. package/dist/promptEnhancer/generator.js +424 -0
  53. package/dist/promptEnhancer/index.d.ts +16 -0
  54. package/dist/promptEnhancer/index.d.ts.map +1 -0
  55. package/dist/promptEnhancer/index.js +15 -0
  56. package/dist/promptEnhancer/optimize.d.ts +27 -0
  57. package/dist/promptEnhancer/optimize.d.ts.map +1 -0
  58. package/dist/promptEnhancer/optimize.js +203 -0
  59. package/dist/promptEnhancer/planner.d.ts +36 -0
  60. package/dist/promptEnhancer/planner.d.ts.map +1 -0
  61. package/dist/promptEnhancer/planner.js +55 -0
  62. package/dist/promptEnhancer/profiles.d.ts +20 -0
  63. package/dist/promptEnhancer/profiles.d.ts.map +1 -0
  64. package/dist/promptEnhancer/profiles.js +126 -0
  65. package/dist/promptEnhancer/rendering.d.ts +15 -0
  66. package/dist/promptEnhancer/rendering.d.ts.map +1 -0
  67. package/dist/promptEnhancer/rendering.js +72 -0
  68. package/dist/promptEnhancer/validate.d.ts +31 -0
  69. package/dist/promptEnhancer/validate.d.ts.map +1 -0
  70. package/dist/promptEnhancer/validate.js +144 -0
  71. package/dist/qualityIntelligence/budget.d.ts +10 -0
  72. package/dist/qualityIntelligence/budget.d.ts.map +1 -0
  73. package/dist/qualityIntelligence/budget.js +38 -0
  74. package/dist/qualityIntelligence/cancellation.d.ts +7 -0
  75. package/dist/qualityIntelligence/cancellation.d.ts.map +1 -0
  76. package/dist/qualityIntelligence/cancellation.js +58 -0
  77. package/dist/qualityIntelligence/capabilityGate.d.ts +13 -0
  78. package/dist/qualityIntelligence/capabilityGate.d.ts.map +1 -0
  79. package/dist/qualityIntelligence/capabilityGate.js +51 -0
  80. package/dist/qualityIntelligence/capabilityMapping.d.ts +4 -0
  81. package/dist/qualityIntelligence/capabilityMapping.d.ts.map +1 -0
  82. package/dist/qualityIntelligence/capabilityMapping.js +21 -0
  83. package/dist/qualityIntelligence/circuitBreaker.d.ts +26 -0
  84. package/dist/qualityIntelligence/circuitBreaker.d.ts.map +1 -0
  85. package/dist/qualityIntelligence/circuitBreaker.js +78 -0
  86. package/dist/qualityIntelligence/dispatcher.d.ts +38 -0
  87. package/dist/qualityIntelligence/dispatcher.d.ts.map +1 -0
  88. package/dist/qualityIntelligence/dispatcher.js +116 -0
  89. package/dist/qualityIntelligence/index.d.ts +20 -0
  90. package/dist/qualityIntelligence/index.d.ts.map +1 -0
  91. package/dist/qualityIntelligence/index.js +15 -0
  92. package/dist/qualityIntelligence/promptSegmentation.d.ts +13 -0
  93. package/dist/qualityIntelligence/promptSegmentation.d.ts.map +1 -0
  94. package/dist/qualityIntelligence/promptSegmentation.js +70 -0
  95. package/dist/qualityIntelligence/replayCache.d.ts +11 -0
  96. package/dist/qualityIntelligence/replayCache.d.ts.map +1 -0
  97. package/dist/qualityIntelligence/replayCache.js +72 -0
  98. package/dist/qualityIntelligence/routing.d.ts +11 -0
  99. package/dist/qualityIntelligence/routing.d.ts.map +1 -0
  100. package/dist/qualityIntelligence/routing.js +25 -0
  101. package/dist/qualityIntelligence/safeError.d.ts +38 -0
  102. package/dist/qualityIntelligence/safeError.d.ts.map +1 -0
  103. package/dist/qualityIntelligence/safeError.js +63 -0
  104. package/dist/qualityIntelligence/taskProfiles.d.ts +15 -0
  105. package/dist/qualityIntelligence/taskProfiles.d.ts.map +1 -0
  106. package/dist/qualityIntelligence/taskProfiles.js +101 -0
  107. package/dist/resilience.d.ts +26 -0
  108. package/dist/resilience.d.ts.map +1 -0
  109. package/dist/resilience.js +182 -0
  110. package/dist/types.d.ts +59 -0
  111. package/dist/types.d.ts.map +1 -0
  112. package/dist/types.js +6 -0
  113. package/dist/version.d.ts +2 -0
  114. package/dist/version.d.ts.map +1 -0
  115. package/dist/version.js +3 -0
  116. package/package.json +47 -0
@@ -0,0 +1,109 @@
1
+ // Prompt Enhancer candidate generation (Epic #1307, Issue #1312; ADR-0044 §1/§5/§6).
2
+ //
3
+ // Produces a bounded, ordered set of distinct, safety-preserving Enhanced Prompt candidates for one
4
+ // analysis by varying the generation profile across a deterministic slate. Each profile yields a
5
+ // structurally different prompt (reasoning strategy/depth, token budget, structural caps — see
6
+ // `profiles.ts`), giving the critic genuinely different candidates to rank.
7
+ //
8
+ // Safety floor (AC5 — candidate generation never relaxes safety, grounding, or output schema): the
9
+ // baseline plan's safety posture is the floor. A candidate is admitted only if its derived posture is
10
+ // at least as strict (a candidate that would drop the safety-critical disclaimer or the human-approval
11
+ // gate is rejected before it is ever scored). Output schema and grounding need are bound to the
12
+ // analysis by `generateEnhancedPrompt`, so they cannot drift across candidates.
13
+ //
14
+ // Because the planner escalates a critical-criticality task to the `safety-critical` profile regardless
15
+ // of preference, such tasks deterministically collapse to a single candidate — relaxing the profile to
16
+ // manufacture variety would relax safety, which AC5 forbids. The "at least three candidates" capability
17
+ // (AC1) therefore applies to tasks whose safety floor admits variation, which is the common case.
18
+ //
19
+ // Determinism: pure. No IO, clock, or randomness.
20
+ import { asEnhancedPromptId, PROMPT_ENHANCEMENT_PROFILE_IDS, } from "@oscharko-dev/keiko-contracts";
21
+ import { canonicalise, sha256Hex } from "@oscharko-dev/keiko-security";
22
+ import { generateEnhancedPrompt } from "./generator.js";
23
+ import { planPromptEnhancement } from "./planner.js";
24
+ function candidateId(analysis, profile) {
25
+ const digest = sha256Hex(canonicalise({ profile, requestId: analysis.requestId }));
26
+ return `pe-candidate-${profile}-${digest}`;
27
+ }
28
+ // The candidate-preference slate: the baseline profile first, then every other catalog profile in its
29
+ // canonical order. Planning each preference yields the actual selected profile (which may be escalated).
30
+ function preferenceSlate(baseline) {
31
+ const rest = PROMPT_ENHANCEMENT_PROFILE_IDS.filter((id) => id !== baseline);
32
+ return [baseline, ...rest];
33
+ }
34
+ // A candidate preserves the safety floor when its posture is at least as strict as the baseline on both
35
+ // safety-critical handling and the human-approval gate. (Output schema and grounding need are bound to
36
+ // the analysis by the generator, so they never drift.)
37
+ function preservesSafetyFloor(candidate, floor) {
38
+ const safetyCriticalOk = candidate.safetyPosture.safetyCritical || !floor.safetyPosture.safetyCritical;
39
+ const humanApprovalOk = candidate.safetyPosture.requiresHumanApproval || !floor.safetyPosture.requiresHumanApproval;
40
+ return safetyCriticalOk && humanApprovalOk && candidate.safetyPosture.restrictsAuthority;
41
+ }
42
+ // A wording-independent structural signature of the prompt, used to drop a candidate that is identical
43
+ // to a higher-priority one even though its nominal profile differs.
44
+ function structuralSignature(prompt) {
45
+ return JSON.stringify([
46
+ prompt.role,
47
+ prompt.goal,
48
+ prompt.context,
49
+ prompt.taskDecomposition,
50
+ prompt.constraints,
51
+ prompt.groundingRules,
52
+ prompt.qualityCriteria,
53
+ prompt.uncertaintyHandling,
54
+ prompt.safetyRules,
55
+ prompt.outputSchema,
56
+ prompt.groundingPlan,
57
+ ]);
58
+ }
59
+ /**
60
+ * Generate a bounded, ordered set of distinct, safety-preserving Enhanced Prompt candidates. Pure.
61
+ *
62
+ * Candidates are produced by planning the analysis under each profile in the slate, deduplicating by
63
+ * the actual selected profile (so a forced escalation does not produce repeats), rejecting any candidate
64
+ * that would relax the baseline safety floor, and capping the result at `candidateCount`.
65
+ */
66
+ export function generatePromptCandidates(args) {
67
+ const { analysis, input } = args;
68
+ const count = Number.isInteger(args.candidateCount) && args.candidateCount > 0 ? args.candidateCount : 1;
69
+ const floor = planPromptEnhancement(analysis, { profilePreference: args.profilePreference });
70
+ const baselineProfile = floor.selectedProfile;
71
+ const candidates = [];
72
+ const rejected = [];
73
+ const seenProfiles = new Set();
74
+ const seenSignatures = new Set();
75
+ for (const preference of preferenceSlate(baselineProfile)) {
76
+ if (candidates.length >= count)
77
+ break;
78
+ const plan = planPromptEnhancement(analysis, { profilePreference: preference });
79
+ const actualProfile = plan.selectedProfile;
80
+ // Deduplicate by the actual selected profile: a forced escalation (e.g. every preference escalating
81
+ // to `safety-critical` for a critical task) is the same candidate, generated once.
82
+ if (seenProfiles.has(actualProfile))
83
+ continue;
84
+ seenProfiles.add(actualProfile);
85
+ const id = candidateId(analysis, actualProfile);
86
+ if (!preservesSafetyFloor(plan, floor)) {
87
+ rejected.push({
88
+ candidateId: id,
89
+ profile: actualProfile,
90
+ reason: "safety-floor-not-preserved",
91
+ });
92
+ continue;
93
+ }
94
+ const prompt = generateEnhancedPrompt({
95
+ promptId: asEnhancedPromptId(id),
96
+ analysis,
97
+ plan,
98
+ input,
99
+ });
100
+ const signature = structuralSignature(prompt);
101
+ if (seenSignatures.has(signature)) {
102
+ rejected.push({ candidateId: id, profile: actualProfile, reason: "duplicate-candidate" });
103
+ continue;
104
+ }
105
+ seenSignatures.add(signature);
106
+ candidates.push({ candidateId: id, profile: actualProfile, plan, prompt });
107
+ }
108
+ return { candidates, rejected };
109
+ }
@@ -0,0 +1,22 @@
1
+ import { type EnhancedPrompt, type PromptCandidateScorecard, type PromptCriticDimension, type PromptEnhancementProfileId, type PromptTaskAnalysis } from "@oscharko-dev/keiko-contracts";
2
+ import type { PromptEnhancementPlan } from "./planner.js";
3
+ export declare const PROMPT_CRITIC_DIMENSION_WEIGHTS: Readonly<Record<PromptCriticDimension, number>>;
4
+ export interface PromptCandidateScoringContext {
5
+ readonly candidateId: string;
6
+ readonly profile: PromptEnhancementProfileId;
7
+ readonly prompt: EnhancedPrompt;
8
+ readonly plan: PromptEnhancementPlan;
9
+ readonly analysis: PromptTaskAnalysis;
10
+ }
11
+ /**
12
+ * Deterministic token estimate for the full rendered prompt (instructions + input). Heuristic, not a
13
+ * provider tokenizer. This is the candidate's real dispatch-cost estimate and feeds the budget.
14
+ */
15
+ export declare function estimatePromptTokens(prompt: EnhancedPrompt): number;
16
+ /**
17
+ * Deterministically score one Enhanced Prompt candidate across all six critic dimensions and fold them
18
+ * into a weighted aggregate. Pure. The result is a fully populated, auditable `PromptCandidateScorecard`
19
+ * with one score-plus-rationale per dimension in canonical order.
20
+ */
21
+ export declare function scorePromptCandidate(context: PromptCandidateScoringContext): PromptCandidateScorecard;
22
+ //# sourceMappingURL=critic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"critic.d.ts","sourceRoot":"","sources":["../../src/promptEnhancer/critic.ts"],"names":[],"mappings":"AAeA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAE1B,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACxB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAuB1D,eAAO,MAAM,+BAA+B,EAAE,QAAQ,CAAC,MAAM,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAQxF,CAAC;AAIL,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,0BAA0B,CAAC;IAC7C,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;CACvC;AA4BD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAEnE;AAqKD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,6BAA6B,GACrC,wBAAwB,CAmB1B"}
@@ -0,0 +1,237 @@
1
+ // Prompt Enhancer deterministic PromptCritic (Epic #1307, Issue #1312; ADR-0044 §1/§6).
2
+ //
3
+ // Scores an `EnhancedPrompt` candidate on the six Issue #1312 quality dimensions — clarity,
4
+ // completeness, grounding readiness, safety, output controllability, and token efficiency — each on a
5
+ // continuous [0, 1] scale with an inspectable rationale, then folds them into a single weighted
6
+ // aggregate used for ranking. The MVP critic is fully deterministic so it provides reproducible CI
7
+ // coverage (Engineering Notes: "MVP scoring must have deterministic coverage for CI"); a model-assisted
8
+ // LLM-as-judge stage is an explicit later option and is intentionally not built here.
9
+ //
10
+ // The score is computed from observable structural evidence of the artefact (section population,
11
+ // decomposition depth, grounding apparatus, safety-rule coverage, output-schema shape, token estimate),
12
+ // never from the raw user input, so a rationale can never echo untrusted content (ADR-0044 §5).
13
+ //
14
+ // Determinism: pure. No IO, clock, or randomness. Identical inputs always yield an identical scorecard.
15
+ import { PROMPT_CRITIC_DIMENSIONS, PROMPT_ENHANCER_SCHEMA_VERSION, } from "@oscharko-dev/keiko-contracts";
16
+ import { renderEnhancedPromptText } from "./rendering.js";
17
+ // Heuristic characters-per-token ratio for the deterministic token estimate. This is a coarse, model
18
+ // independent approximation (not a provider tokenizer); it is used consistently for every candidate so
19
+ // the comparison between candidates is fair, and it feeds the optimization-loop token budget.
20
+ const CHARS_PER_TOKEN = 4;
21
+ // Global normalization references for the absolute-thoroughness dimensions. Chosen as the largest cap
22
+ // any execution profile applies (see `profiles.ts`), so a maximally thorough prompt approaches 1.0.
23
+ const REFERENCE_DECOMPOSITION_STEPS = 6;
24
+ const REFERENCE_QUALITY_CRITERIA = 5;
25
+ const REFERENCE_CONSTRAINTS = 6;
26
+ const REFERENCE_UNCERTAINTY_RULES = 3;
27
+ // A generous instruction-token reference: a lean prompt sits well under this and scores high token
28
+ // efficiency; a rich one approaches it and scores lower, expressing the genuine breadth/cost trade-off.
29
+ const REFERENCE_INSTRUCTION_TOKENS = 600;
30
+ // Token-efficiency never collapses fully to zero for a well-formed prompt; this floor keeps the
31
+ // dimension a graded signal rather than a cliff for the most verbose profile.
32
+ const TOKEN_EFFICIENCY_FLOOR = 0.2;
33
+ // Weighted aggregate. Safety carries the most weight (it is the floor candidate generation must never
34
+ // relax, AC5); completeness is next; the remaining quality dimensions are balanced. Sums to 1.0.
35
+ export const PROMPT_CRITIC_DIMENSION_WEIGHTS = Object.freeze({
36
+ clarity: 0.15,
37
+ completeness: 0.2,
38
+ "grounding-readiness": 0.15,
39
+ safety: 0.25,
40
+ "output-controllability": 0.15,
41
+ "token-efficiency": 0.1,
42
+ });
43
+ // ─── Deterministic numeric helpers ─────────────────────────────────────────────────
44
+ function clampUnit(value) {
45
+ if (!Number.isFinite(value) || value <= 0)
46
+ return 0;
47
+ if (value >= 1)
48
+ return 1;
49
+ return value;
50
+ }
51
+ // Round to 4 decimals so scores are stable, readable values rather than IEEE-754 tails. Deterministic.
52
+ function round4(value) {
53
+ return Math.round(value * 10_000) / 10_000;
54
+ }
55
+ function mean(values) {
56
+ if (values.length === 0)
57
+ return 0;
58
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
59
+ }
60
+ function estimateTokens(text) {
61
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
62
+ }
63
+ /**
64
+ * Deterministic token estimate for the full rendered prompt (instructions + input). Heuristic, not a
65
+ * provider tokenizer. This is the candidate's real dispatch-cost estimate and feeds the budget.
66
+ */
67
+ export function estimatePromptTokens(prompt) {
68
+ return estimateTokens(renderEnhancedPromptText(prompt));
69
+ }
70
+ // The instruction overhead the candidate controls: every trusted section except the constant user
71
+ // input. Token efficiency is scored on this so a candidate is not penalised for the input's size, which
72
+ // is identical across all candidates for the same request.
73
+ function instructionTokens(prompt) {
74
+ const sections = [
75
+ prompt.role,
76
+ prompt.goal,
77
+ ...prompt.context,
78
+ ...prompt.taskDecomposition,
79
+ ...prompt.constraints,
80
+ ...prompt.groundingRules,
81
+ ...prompt.qualityCriteria,
82
+ ...prompt.uncertaintyHandling,
83
+ ...prompt.safetyRules,
84
+ ];
85
+ return estimateTokens(sections.join("\n"));
86
+ }
87
+ // ─── Dimension scorers ─────────────────────────────────────────────────────────────
88
+ // Clarity: the prompt is unambiguous and well organised — every structural slot is populated and the
89
+ // task is expressed as an ordered, multi-step plan. Well-formed prompts score uniformly high here;
90
+ // clarity is a stable base rather than the primary differentiator.
91
+ function scoreClarity(prompt) {
92
+ const checks = [
93
+ prompt.role.trim().length > 0,
94
+ prompt.goal.trim().length > 0,
95
+ prompt.context.length > 0,
96
+ prompt.taskDecomposition.length >= 2,
97
+ prompt.constraints.length > 0,
98
+ prompt.qualityCriteria.length > 0,
99
+ prompt.uncertaintyHandling.length > 0,
100
+ ];
101
+ const satisfied = checks.filter(Boolean).length;
102
+ return {
103
+ score: clampUnit(satisfied / checks.length),
104
+ rationale: `${String(satisfied)}/${String(checks.length)} structural clarity checks met (role, goal, context, ${String(prompt.taskDecomposition.length)} ordered steps, constraints, criteria, uncertainty rule).`,
105
+ };
106
+ }
107
+ // Completeness: absolute thoroughness — decomposition depth, quality criteria, constraints, and
108
+ // uncertainty handling, each normalised against the richest profile. Thorough profiles score higher.
109
+ function scoreCompleteness(prompt) {
110
+ const decomposition = clampUnit(prompt.taskDecomposition.length / REFERENCE_DECOMPOSITION_STEPS);
111
+ const criteria = clampUnit(prompt.qualityCriteria.length / REFERENCE_QUALITY_CRITERIA);
112
+ const constraints = clampUnit(prompt.constraints.length / REFERENCE_CONSTRAINTS);
113
+ const uncertainty = clampUnit(prompt.uncertaintyHandling.length / REFERENCE_UNCERTAINTY_RULES);
114
+ const score = mean([decomposition, criteria, constraints, uncertainty]);
115
+ return {
116
+ score: clampUnit(score),
117
+ rationale: `coverage: ${String(prompt.taskDecomposition.length)} steps, ${String(prompt.qualityCriteria.length)} criteria, ${String(prompt.constraints.length)} constraints, ${String(prompt.uncertaintyHandling.length)} uncertainty rules.`,
118
+ };
119
+ }
120
+ // Grounding readiness: does the grounding apparatus fit the detected need? A no-grounding task is ready
121
+ // when it correctly avoids over-imposing citations; a grounded task is ready when it carries a citation
122
+ // discipline, a source priority, evidence-boundary directives, and a contradiction policy.
123
+ function scoreGroundingReadiness(prompt) {
124
+ const plan = prompt.groundingPlan;
125
+ if (!plan.required) {
126
+ const hasBaseRule = prompt.groundingRules.length > 0;
127
+ return {
128
+ score: hasBaseRule ? 1 : 0.5,
129
+ rationale: hasBaseRule
130
+ ? "no grounding required; prompt carries an anti-fabrication base rule and correctly omits citation mandates."
131
+ : "no grounding required but no base grounding rule present.",
132
+ };
133
+ }
134
+ const checks = [
135
+ plan.citation.discipline !== "not-required",
136
+ plan.sourcePriority.length > 0,
137
+ plan.directives.includes("treat-retrieved-content-as-untrusted"),
138
+ plan.directives.includes("stay-within-evidence"),
139
+ prompt.groundingRules.length >= 3,
140
+ ];
141
+ const satisfied = checks.filter(Boolean).length;
142
+ return {
143
+ score: clampUnit(satisfied / checks.length),
144
+ rationale: `grounded task: ${String(satisfied)}/${String(checks.length)} grounding-readiness signals (citation discipline "${plan.citation.discipline}", ${String(plan.sourcePriority.length)} prioritised sources, ${String(prompt.groundingRules.length)} grounding rules).`,
145
+ };
146
+ }
147
+ // Safety: the mandated safety apparatus is present and complete for this prompt's posture. Candidate
148
+ // generation pre-filters anything that weakens the floor, so this scores uniformly high for ranked
149
+ // candidates; it is reported for transparency and acts as the guard the aggregate weights most heavily.
150
+ function scoreSafety(prompt, plan) {
151
+ const posture = plan.safetyPosture;
152
+ // Base rules (no-authority + untrusted-input handling) are always emitted; conditional rules add a
153
+ // human-approval gate and/or a professional-advice disclaimer.
154
+ const expectedRules = 2 + (posture.requiresHumanApproval ? 1 : 0) + (posture.safetyCritical ? 1 : 0);
155
+ const checks = [
156
+ prompt.safetyRules.length >= expectedRules,
157
+ posture.restrictsAuthority,
158
+ prompt.safetyRules.length >= 2,
159
+ ];
160
+ const satisfied = checks.filter(Boolean).length;
161
+ return {
162
+ score: clampUnit(satisfied / checks.length),
163
+ rationale: `${String(prompt.safetyRules.length)}/${String(expectedRules)} expected safety rules present (humanApproval=${String(posture.requiresHumanApproval)}, safetyCritical=${String(posture.safetyCritical)}, restrictsAuthority=${String(posture.restrictsAuthority)}).`,
164
+ };
165
+ }
166
+ // Output controllability: how tightly the prompt pins the output form. Structured schemas with format
167
+ // hints and an explicit controllability criterion are highly controllable; inherently freeform tasks
168
+ // score low by nature (an honest reflection, constant across that task's candidates).
169
+ function scoreOutputControllability(prompt) {
170
+ const schema = prompt.outputSchema;
171
+ const hasControllabilityCriterion = prompt.qualityCriteria.some((criterion) => criterion.startsWith("Output controllability"));
172
+ const checks = [
173
+ schema.structured,
174
+ schema.hints.length > 0,
175
+ hasControllabilityCriterion,
176
+ ];
177
+ const satisfied = checks.filter(Boolean).length;
178
+ return {
179
+ score: clampUnit(satisfied / checks.length),
180
+ rationale: `output ${schema.structured ? "structured" : "unstructured"} (${schema.format}), ${String(schema.hints.length)} hints, controllability criterion ${hasControllabilityCriterion ? "present" : "absent"}.`,
181
+ };
182
+ }
183
+ // Token efficiency: instruction leanness. Scored on instruction overhead only (the input is constant
184
+ // across candidates), normalised against a generous reference and floored so the richest profile still
185
+ // grades rather than collapsing. Lean profiles score high; this trades off against completeness.
186
+ function scoreTokenEfficiency(prompt) {
187
+ const tokens = instructionTokens(prompt);
188
+ const raw = 1 - tokens / REFERENCE_INSTRUCTION_TOKENS;
189
+ const score = TOKEN_EFFICIENCY_FLOOR + (1 - TOKEN_EFFICIENCY_FLOOR) * clampUnit(raw);
190
+ return {
191
+ score: clampUnit(score),
192
+ rationale: `~${String(tokens)} instruction tokens (reference ${String(REFERENCE_INSTRUCTION_TOKENS)}); leaner instructions score higher.`,
193
+ };
194
+ }
195
+ function assess(dimension, context) {
196
+ switch (dimension) {
197
+ case "clarity":
198
+ return scoreClarity(context.prompt);
199
+ case "completeness":
200
+ return scoreCompleteness(context.prompt);
201
+ case "grounding-readiness":
202
+ return scoreGroundingReadiness(context.prompt);
203
+ case "safety":
204
+ return scoreSafety(context.prompt, context.plan);
205
+ case "output-controllability":
206
+ return scoreOutputControllability(context.prompt);
207
+ case "token-efficiency":
208
+ return scoreTokenEfficiency(context.prompt);
209
+ }
210
+ }
211
+ function aggregate(scores) {
212
+ const total = scores.reduce((sum, entry) => sum + PROMPT_CRITIC_DIMENSION_WEIGHTS[entry.dimension] * entry.score, 0);
213
+ return round4(clampUnit(total));
214
+ }
215
+ /**
216
+ * Deterministically score one Enhanced Prompt candidate across all six critic dimensions and fold them
217
+ * into a weighted aggregate. Pure. The result is a fully populated, auditable `PromptCandidateScorecard`
218
+ * with one score-plus-rationale per dimension in canonical order.
219
+ */
220
+ export function scorePromptCandidate(context) {
221
+ const dimensionScores = PROMPT_CRITIC_DIMENSIONS.map((dimension) => {
222
+ const assessment = assess(dimension, context);
223
+ return {
224
+ dimension,
225
+ score: round4(clampUnit(assessment.score)),
226
+ rationale: assessment.rationale,
227
+ };
228
+ });
229
+ return {
230
+ schemaVersion: PROMPT_ENHANCER_SCHEMA_VERSION,
231
+ candidateId: context.candidateId,
232
+ profile: context.profile,
233
+ dimensionScores,
234
+ aggregateScore: aggregate(dimensionScores),
235
+ estimatedTokens: estimatePromptTokens(context.prompt),
236
+ };
237
+ }
@@ -0,0 +1,15 @@
1
+ import { type EnhancedPrompt, type EnhancedPromptId, type PromptTaskAnalysis, type RawPromptInput } from "@oscharko-dev/keiko-contracts";
2
+ import type { PromptEnhancementPlan } from "./planner.js";
3
+ export declare const GENERATED_INPUT_MAX_CHARS = 16000;
4
+ export interface GenerateEnhancedPromptArgs {
5
+ readonly promptId: EnhancedPromptId;
6
+ readonly analysis: PromptTaskAnalysis;
7
+ readonly plan: PromptEnhancementPlan;
8
+ readonly input: RawPromptInput;
9
+ }
10
+ /**
11
+ * Generate a deterministic, structured `EnhancedPrompt` from an analysis, its plan, and the raw input.
12
+ * Pure. The result always satisfies `validateEnhancedPrompt` (#1309).
13
+ */
14
+ export declare function generateEnhancedPrompt(args: GenerateEnhancedPromptArgs): EnhancedPrompt;
15
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/promptEnhancer/generator.ts"],"names":[],"mappings":"AAmBA,OAAO,EAOL,KAAK,cAAc,EACnB,KAAK,gBAAgB,EAUrB,KAAK,kBAAkB,EAEvB,KAAK,cAAc,EACpB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAO1D,eAAO,MAAM,yBAAyB,QAAS,CAAC;AA8chD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,0BAA0B,GAAG,cAAc,CAwBvF"}