incremnt 0.8.7 → 0.8.8

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 (60) hide show
  1. package/README.md +2 -1
  2. package/SKILL.md +2 -1
  3. package/package.json +4 -1
  4. package/src/ask-answer-verifier.js +23 -2
  5. package/src/ask-coach/contexts.js +1472 -0
  6. package/src/ask-coach/evidence-plan.js +342 -0
  7. package/src/ask-coach/observations.js +8 -0
  8. package/src/ask-coach/orchestrator.js +1641 -0
  9. package/src/ask-coach/renderers.js +4 -0
  10. package/src/ask-coach/routing.js +610 -0
  11. package/src/ask-coach/structured-response.js +5 -0
  12. package/src/ask-coach-routing.js +4 -0
  13. package/src/ask-coach.js +26 -3506
  14. package/src/ask-replay.js +70 -4
  15. package/src/ask-starter-prompts.js +206 -0
  16. package/src/auth.js +26 -0
  17. package/src/coach-advice.js +32 -0
  18. package/src/coach-facts.js +214 -0
  19. package/src/coach-prompt-layers.js +26 -30
  20. package/src/contract.js +27 -7
  21. package/src/exercise-aliases.js +6 -0
  22. package/src/format.js +17 -0
  23. package/src/index.js +1 -1
  24. package/src/lib.js +41 -36
  25. package/src/mcp.js +1 -1
  26. package/src/openrouter.js +279 -161
  27. package/src/plan-changeset.js +14 -8
  28. package/src/program-draft.js +97 -1
  29. package/src/prompt-changelog.js +16 -0
  30. package/src/prompt-security.js +34 -3
  31. package/src/promptfoo-evals.js +2 -0
  32. package/src/promptfoo-langfuse-scores.js +10 -0
  33. package/src/prompts/ask.js +22 -0
  34. package/src/prompts/checkpoint.js +14 -0
  35. package/src/prompts/coach-facts.js +24 -0
  36. package/src/prompts/cycle.js +23 -0
  37. package/src/prompts/starter-graph.js +7 -0
  38. package/src/prompts/vitals.js +9 -0
  39. package/src/prompts/weekly-checkin.js +20 -0
  40. package/src/prompts/workout.js +47 -0
  41. package/src/queries/coach-observations.js +8 -0
  42. package/src/queries/coach-read-tools.js +6 -0
  43. package/src/queries/commands.js +3 -0
  44. package/src/queries/common.js +9 -0
  45. package/src/queries/core.js +6354 -0
  46. package/src/queries/exercise-identity.js +6 -0
  47. package/src/queries/health.js +12 -0
  48. package/src/queries/programs.js +17 -0
  49. package/src/queries/records-progress.js +10 -0
  50. package/src/queries/sessions.js +12 -0
  51. package/src/queries/weekly.js +5 -0
  52. package/src/queries.js +10 -6283
  53. package/src/remote.js +51 -0
  54. package/src/score-context.js +58 -3
  55. package/src/summary-evals.js +201 -45
  56. package/src/sync-service.js +1118 -104
  57. package/src/training-language-public-terms.json +97 -0
  58. package/src/training-language.js +94 -0
  59. package/src/transport.js +7 -1
  60. package/src/validate.js +11 -1
@@ -0,0 +1,342 @@
1
+ import { classifyAskIntent } from './routing.js';
2
+ import {
3
+ dateOnlyString,
4
+ observationExerciseCandidates,
5
+ relativeDateString,
6
+ uniqueArray
7
+ } from '../queries.js';
8
+
9
+ const ASK_FACT_KIND_BY_ROUTE = Object.freeze({
10
+ general: ['goal_signal', 'preference', 'constraint', 'injury', 'tone'],
11
+ progress_review: ['goal_signal', 'preference', 'constraint', 'injury', 'tone'],
12
+ exercise_progress: ['goal_signal', 'injury', 'constraint', 'preference', 'tone'],
13
+ exercise_progress_summary: ['goal_signal', 'injury', 'constraint', 'preference', 'tone'],
14
+ program_progress: ['goal_signal', 'preference', 'constraint', 'injury', 'tone'],
15
+ program_schedule_action: ['goal_signal', 'preference', 'constraint', 'injury', 'tone'],
16
+ training_profile: ['goal_signal', 'preference', 'constraint', 'injury', 'tone'],
17
+ program_design: ['goal_signal', 'preference', 'constraint', 'injury', 'tone'],
18
+ next_session: ['constraint', 'injury', 'preference', 'goal_signal', 'tone'],
19
+ recent_session: ['injury', 'constraint', 'goal_signal', 'tone'],
20
+ recovery: ['injury', 'constraint', 'tone'],
21
+ body_weight: ['goal_signal', 'tone'],
22
+ volume: ['goal_signal', 'constraint', 'tone'],
23
+ records: ['goal_signal', 'tone'],
24
+ score: ['goal_signal', 'constraint', 'tone']
25
+ });
26
+
27
+ const ASK_ROUTE_REQUIRED_TOOLS = Object.freeze({
28
+ general: ['get_recent_sessions'],
29
+ progress_review: ['get_athlete_snapshot', 'get_recent_sessions', 'get_weekly_volume', 'get_records', 'get_body_weight_snapshot', 'get_readiness_snapshot'],
30
+ volume: ['get_weekly_volume'],
31
+ next_session: ['get_next_session'],
32
+ exercise_progress: ['get_exercise_history'],
33
+ exercise_progress_summary: ['get_exercise_progress_summary'],
34
+ program_progress: ['get_program_progress', 'get_exercise_progress_summary', 'get_cycle_progression_summary'],
35
+ program_history: ['get_program_change_history'],
36
+ program_schedule_action: ['get_program_progress', 'get_recent_sessions', 'get_readiness_snapshot'],
37
+ training_profile: ['get_training_profile'],
38
+ records: ['get_records'],
39
+ recent_session: ['get_recent_sessions'],
40
+ recovery: ['get_readiness_snapshot', 'get_recent_sessions'],
41
+ body_weight: ['get_body_weight_snapshot'],
42
+ score: ['get_increment_score'],
43
+ program_design: ['get_recent_sessions']
44
+ });
45
+
46
+ const ASK_OBSERVATION_FOLLOW_UP_INTENTS = new Set(['successor_plan', 'plan_adjustment']);
47
+
48
+ const ASK_DYNAMIC_EVIDENCE_MAX_TOOLS = 4;
49
+ const ASK_DYNAMIC_EVIDENCE_ROUTES = new Set(['general', 'recovery', 'training_profile']);
50
+
51
+ const READINESS_OBSERVATION_KINDS = new Set([
52
+ 'recovery_load_spacing_pattern',
53
+ 'health_recovery_drift',
54
+ 'health_recovery_uptrend'
55
+ ]);
56
+
57
+ const BODY_WEIGHT_OBSERVATION_KINDS = new Set([
58
+ 'growth_bodyweight_mismatch',
59
+ 'growth_bodyweight_aligned'
60
+ ]);
61
+
62
+ export function coachFactKindsForAskRoute(route) {
63
+ return ASK_FACT_KIND_BY_ROUTE[route] ?? ASK_FACT_KIND_BY_ROUTE.general;
64
+ }
65
+
66
+ export function coachFactKindsForAskQuestion(snapshot, question, options = {}) {
67
+ const intent = classifyAskIntent(snapshot, question, options);
68
+ const effectiveRoute = intent.effectiveRoute ?? intent.route ?? 'general';
69
+ return coachFactKindsForAskRoute(effectiveRoute);
70
+ }
71
+
72
+ export function requiredToolsForAskRoute(route) {
73
+ return ASK_ROUTE_REQUIRED_TOOLS[route] ?? ASK_ROUTE_REQUIRED_TOOLS.general;
74
+ }
75
+
76
+ function askObservationCheckPlan({ exclude = new Set(), route } = {}) {
77
+ if (exclude.has('coach_observations')) return [];
78
+ const checks = [
79
+ {
80
+ kind: 'current_observations',
81
+ toolName: 'get_current_coach_observations',
82
+ status: 'planned'
83
+ }
84
+ ];
85
+ if (route === 'recent_session') {
86
+ checks.push({
87
+ kind: 'session_observation_reconciliation',
88
+ toolName: 'compare_session_to_observations',
89
+ status: 'conditional',
90
+ requires: ['current_observations']
91
+ });
92
+ }
93
+ return checks;
94
+ }
95
+
96
+ export function shouldUseReadinessForObservation(observation) {
97
+ return READINESS_OBSERVATION_KINDS.has(String(observation?.kind ?? '').trim());
98
+ }
99
+
100
+ export function shouldUseBodyWeightForObservation(observation) {
101
+ return BODY_WEIGHT_OBSERVATION_KINDS.has(String(observation?.kind ?? '').trim());
102
+ }
103
+
104
+ export function askObservationFollowUpRequiredTools(observation) {
105
+ const tools = ['get_recent_sessions', 'compare_session_to_observations'];
106
+ const exercises = observationExerciseCandidates(observation);
107
+ if (exercises.length > 0) tools.push('get_exercise_history');
108
+ if (shouldUseReadinessForObservation(observation)) tools.push('get_readiness_snapshot');
109
+ if (shouldUseBodyWeightForObservation(observation)) tools.push('get_body_weight_snapshot');
110
+ return uniqueArray(tools);
111
+ }
112
+
113
+ export function normalizeObservationFollowUpIntent(intent) {
114
+ const normalized = String(intent ?? '')
115
+ .trim()
116
+ .toLowerCase()
117
+ .replace(/[\s-]+/g, '_');
118
+ return ASK_OBSERVATION_FOLLOW_UP_INTENTS.has(normalized) ? normalized : null;
119
+ }
120
+
121
+ export function observationFollowUpChecks(requiredTools) {
122
+ return requiredTools.map((toolName) => ({
123
+ kind: 'observation_followup_verification',
124
+ toolName,
125
+ status: 'planned'
126
+ }));
127
+ }
128
+
129
+ function askRouteMissingDataFlagsForTool(tool) {
130
+ if (tool?.toolName === 'get_athlete_snapshot') return [];
131
+ return tool?.missingDataFlags ?? [];
132
+ }
133
+
134
+ function evidenceGapsFromTools(tools = []) {
135
+ return uniqueArray(tools.flatMap(askRouteMissingDataFlagsForTool));
136
+ }
137
+
138
+ export function immutableArray(values = []) {
139
+ return Object.freeze([...values]);
140
+ }
141
+
142
+ export function immutableObservationChecks(checks = []) {
143
+ return immutableArray(checks.map((check) => Object.freeze({ ...check })));
144
+ }
145
+
146
+ function executedObservationChecks(plan, tools = []) {
147
+ const toolByName = new Map(tools.map((tool) => [tool.toolName, tool]));
148
+ return (plan.observationChecks ?? []).map((check) => {
149
+ const tool = toolByName.get(check.toolName);
150
+ if (!tool) return check;
151
+ const hasEvidence = ['get_current_coach_observations', 'compare_session_to_observations'].includes(check.toolName)
152
+ ? (tool.rows?.length ?? 0) > 0
153
+ : (tool.rows?.length ?? 0) > 0 || Object.keys(tool.facts ?? {}).length > 0;
154
+ return {
155
+ ...check,
156
+ status: hasEvidence ? 'executed' : 'no_evidence'
157
+ };
158
+ });
159
+ }
160
+
161
+ export function finalizeEvidencePlan(plan, tools = []) {
162
+ const required = new Set(plan.requiredTools ?? []);
163
+ const requiredTools = tools.filter((tool) => required.has(tool.toolName));
164
+ const dynamic = new Set((plan.dynamicTools ?? []).map((item) => item.toolName));
165
+ const expansive = new Set((plan.expansiveTools ?? []).map((item) => item.toolName));
166
+ return {
167
+ ...plan,
168
+ requiredTools: immutableArray(plan.requiredTools),
169
+ optionalTools: immutableArray(plan.optionalTools),
170
+ dynamicTools: immutableArray(plan.dynamicTools),
171
+ expansiveTools: immutableArray(plan.expansiveTools ?? []),
172
+ observationChecks: immutableObservationChecks(executedObservationChecks(plan, tools)),
173
+ executedTools: immutableArray(uniqueArray(tools.map((tool) => tool.toolName))),
174
+ executedDynamicTools: immutableArray(uniqueArray(tools
175
+ .filter((tool) => dynamic.has(tool.toolName))
176
+ .map((tool) => tool.toolName))),
177
+ executedExpansiveTools: immutableArray(uniqueArray(tools
178
+ .filter((tool) => expansive.has(tool.toolName))
179
+ .map((tool) => tool.toolName))),
180
+ evidenceGaps: immutableArray(evidenceGapsFromTools(requiredTools))
181
+ };
182
+ }
183
+
184
+ function athleteSnapshotExclude(exclude = new Set()) {
185
+ const mapped = new Set(exclude);
186
+ if (exclude.has('bodyWeight')) mapped.add('bodyWeight');
187
+ if (exclude.has('recovery_metrics')) mapped.add('recovery_metrics');
188
+ return [...mapped];
189
+ }
190
+
191
+ function planDynamicAskEvidence({ effectiveRoute, question, exclude = new Set(), since = null, today = new Date() } = {}) {
192
+ if (!ASK_DYNAMIC_EVIDENCE_ROUTES.has(effectiveRoute)) return [];
193
+ const text = String(question ?? '').toLowerCase();
194
+ const plans = [];
195
+ const add = (toolName, section, params, reason) => {
196
+ if (plans.length >= ASK_DYNAMIC_EVIDENCE_MAX_TOOLS) return;
197
+ if (plans.some((item) => item.toolName === toolName)) return;
198
+ plans.push(Object.freeze({ toolName, section, params, reason }));
199
+ };
200
+
201
+ add('get_athlete_snapshot', 'athlete_snapshot', {
202
+ today,
203
+ windowDays: since ? 60 : 35,
204
+ exclude: athleteSnapshotExclude(exclude)
205
+ }, 'broad_warm_start');
206
+
207
+ if (/\b(focus|next|priority|prioritise|prioritize|story|honest|doing|bigger|coaching|read|balance|balanced|volume|muscle|coverage|training)\b/i.test(text)) {
208
+ add('get_muscle_volume_trend', 'muscle_volume_trend', { today, weeks: 4 }, 'training_balance_or_focus_question');
209
+ }
210
+
211
+ if (/\b(tired|fatigue|recovery|recover|readiness|strain|hard|too much|sick|sleep|hrv|resting)\b/i.test(text)) {
212
+ add('get_readiness_snapshot', 'readiness', {
213
+ recentDays: 14,
214
+ exclude: [...exclude],
215
+ today
216
+ }, 'recovery_or_load_question');
217
+ }
218
+
219
+ if (/\b(progress|stronger|strength|moving|stall|stalled|pr|prs|record|records|improve|improving)\b/i.test(text)) {
220
+ add('get_records', 'records', {
221
+ exercises: [],
222
+ limit: 10,
223
+ recentSince: since ?? relativeDateString(today, -35),
224
+ today
225
+ }, 'progress_or_records_question');
226
+ }
227
+
228
+ if (/\b(body ?weight|weight|lean|bulk|cut|scale)\b/i.test(text)) {
229
+ add('get_body_weight_snapshot', 'body_weight', {
230
+ recentDays: 30,
231
+ exclude: [...exclude],
232
+ today
233
+ }, 'body_weight_question');
234
+ }
235
+
236
+ return plans;
237
+ }
238
+
239
+ export function planExpansiveAskEvidence({
240
+ enabled = false,
241
+ namedExercises = [],
242
+ existingSections = [],
243
+ omitSections = [],
244
+ exclude = new Set(),
245
+ today = new Date()
246
+ } = {}) {
247
+ if (!enabled) return [];
248
+ const sections = new Set(existingSections);
249
+ const omitted = new Set(omitSections);
250
+ const plans = [];
251
+ const add = (toolName, section, params, reason) => {
252
+ if (sections.has(section) || omitted.has(section)) return;
253
+ if (plans.some((item) => item.toolName === toolName)) return;
254
+ plans.push(Object.freeze({ toolName, section, params, reason }));
255
+ sections.add(section);
256
+ };
257
+
258
+ add('get_weekly_volume', 'weekly_volume', { today }, 'expansive_profile');
259
+ add('get_records', 'records', {
260
+ exercises: namedExercises,
261
+ limit: namedExercises.length > 0 ? Math.max(5, namedExercises.length) : 10,
262
+ today
263
+ }, 'expansive_profile');
264
+ add('get_body_weight_snapshot', 'body_weight', {
265
+ recentDays: 30,
266
+ exclude: [...exclude],
267
+ today
268
+ }, 'expansive_profile');
269
+ add('get_readiness_snapshot', 'readiness', {
270
+ recentDays: 14,
271
+ exclude: [...exclude],
272
+ today
273
+ }, 'expansive_profile');
274
+
275
+ return plans;
276
+ }
277
+
278
+ export function withExpansiveAskEvidencePlan(plan, expansiveTools = []) {
279
+ return {
280
+ ...plan,
281
+ expansiveTools: immutableArray(expansiveTools),
282
+ optionalTools: immutableArray(uniqueArray([
283
+ ...(plan.optionalTools ?? []),
284
+ ...expansiveTools.map((item) => item.toolName)
285
+ ]))
286
+ };
287
+ }
288
+
289
+ export function planAskEvidence(snapshot, question, {
290
+ exclude = new Set(),
291
+ coachObservations = null,
292
+ history = [],
293
+ today = new Date()
294
+ } = {}) {
295
+ const contextSnapshot = Array.isArray(coachObservations)
296
+ ? { ...snapshot, coachObservations }
297
+ : snapshot;
298
+ const intent = classifyAskIntent(contextSnapshot, question, { history, today });
299
+ const route = intent.route;
300
+ const namedExerciseItems = intent.entities.exercises;
301
+ const sessionLabel = intent.entities.sessionLabel ?? null;
302
+ const sessionReference = intent.entities.sessionReference ?? (sessionLabel ? { label: sessionLabel, setHints: [] } : null);
303
+ const since = intent.timeframe?.since ?? null;
304
+ const effectiveRoute = route === 'exercise_progress' && namedExerciseItems.length === 0 ? 'general' : route;
305
+ const fallbackRoute = effectiveRoute === route ? null : effectiveRoute;
306
+ const requiredTools = requiredToolsForAskRoute(effectiveRoute);
307
+ const observationChecks = askObservationCheckPlan({
308
+ exclude,
309
+ route: effectiveRoute
310
+ });
311
+ const requiredToolSet = new Set(requiredTools);
312
+ const dynamicTools = planDynamicAskEvidence({
313
+ effectiveRoute,
314
+ question,
315
+ exclude,
316
+ since,
317
+ today
318
+ }).filter((tool) => !requiredToolSet.has(tool.toolName));
319
+ const optionalTools = uniqueArray([
320
+ ...dynamicTools.map((item) => item.toolName),
321
+ ...observationChecks.map((check) => check.toolName)
322
+ ]);
323
+
324
+ return {
325
+ route,
326
+ effectiveRoute,
327
+ fallbackRoute,
328
+ namedExercises: namedExerciseItems.map((exercise) => exercise.canonical),
329
+ namedExerciseLabels: namedExerciseItems.map((exercise) => exercise.displayName),
330
+ sessionLabel,
331
+ sessionReference,
332
+ since,
333
+ intent,
334
+ requiredTools: immutableArray(requiredTools),
335
+ optionalTools: immutableArray(optionalTools),
336
+ dynamicTools: immutableArray(dynamicTools),
337
+ expansiveTools: immutableArray([]),
338
+ observationChecks: immutableObservationChecks(observationChecks),
339
+ evidenceGaps: immutableArray([]),
340
+ plannedAt: dateOnlyString(today)
341
+ };
342
+ }
@@ -0,0 +1,8 @@
1
+ export {
2
+ askMissingObservationFollowUpContext,
3
+ askObservationFollowUpContext
4
+ } from './orchestrator.js';
5
+
6
+ export {
7
+ normalizeObservationFollowUpIntent
8
+ } from './evidence-plan.js';