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.
- package/README.md +2 -1
- package/SKILL.md +2 -1
- package/package.json +4 -1
- package/src/ask-answer-verifier.js +23 -2
- package/src/ask-coach/contexts.js +1472 -0
- package/src/ask-coach/evidence-plan.js +342 -0
- package/src/ask-coach/observations.js +8 -0
- package/src/ask-coach/orchestrator.js +1641 -0
- package/src/ask-coach/renderers.js +4 -0
- package/src/ask-coach/routing.js +610 -0
- package/src/ask-coach/structured-response.js +5 -0
- package/src/ask-coach-routing.js +4 -0
- package/src/ask-coach.js +26 -3506
- package/src/ask-replay.js +70 -4
- package/src/ask-starter-prompts.js +206 -0
- package/src/auth.js +26 -0
- package/src/coach-advice.js +32 -0
- package/src/coach-facts.js +214 -0
- package/src/coach-prompt-layers.js +26 -30
- package/src/contract.js +27 -7
- package/src/exercise-aliases.js +6 -0
- package/src/format.js +17 -0
- package/src/index.js +1 -1
- package/src/lib.js +41 -36
- package/src/mcp.js +1 -1
- package/src/openrouter.js +279 -161
- package/src/plan-changeset.js +14 -8
- package/src/program-draft.js +97 -1
- package/src/prompt-changelog.js +16 -0
- package/src/prompt-security.js +34 -3
- package/src/promptfoo-evals.js +2 -0
- package/src/promptfoo-langfuse-scores.js +10 -0
- package/src/prompts/ask.js +22 -0
- package/src/prompts/checkpoint.js +14 -0
- package/src/prompts/coach-facts.js +24 -0
- package/src/prompts/cycle.js +23 -0
- package/src/prompts/starter-graph.js +7 -0
- package/src/prompts/vitals.js +9 -0
- package/src/prompts/weekly-checkin.js +20 -0
- package/src/prompts/workout.js +47 -0
- package/src/queries/coach-observations.js +8 -0
- package/src/queries/coach-read-tools.js +6 -0
- package/src/queries/commands.js +3 -0
- package/src/queries/common.js +9 -0
- package/src/queries/core.js +6354 -0
- package/src/queries/exercise-identity.js +6 -0
- package/src/queries/health.js +12 -0
- package/src/queries/programs.js +17 -0
- package/src/queries/records-progress.js +10 -0
- package/src/queries/sessions.js +12 -0
- package/src/queries/weekly.js +5 -0
- package/src/queries.js +10 -6283
- package/src/remote.js +51 -0
- package/src/score-context.js +58 -3
- package/src/summary-evals.js +201 -45
- package/src/sync-service.js +1118 -104
- package/src/training-language-public-terms.json +97 -0
- package/src/training-language.js +94 -0
- package/src/transport.js +7 -1
- package/src/validate.js +11 -1
package/src/remote.js
CHANGED
|
@@ -335,6 +335,7 @@ function endpointForCommand(baseUrl, normalizedCommand, options) {
|
|
|
335
335
|
if (options.from) url.searchParams.set('from', options.from);
|
|
336
336
|
if (options.to) url.searchParams.set('to', options.to);
|
|
337
337
|
if (options.limit) url.searchParams.set('limit', options.limit);
|
|
338
|
+
if (options['include-signals']) url.searchParams.set('includeSignals', 'true');
|
|
338
339
|
return url;
|
|
339
340
|
}
|
|
340
341
|
case 'coach-observations-current': {
|
|
@@ -486,6 +487,20 @@ export async function buildWriteRequest(commandId, options, sessionState) {
|
|
|
486
487
|
}
|
|
487
488
|
|
|
488
489
|
switch (commandId) {
|
|
490
|
+
case 'programs-create': {
|
|
491
|
+
if (!options.file) {
|
|
492
|
+
const error = new Error('--file is required for programs create.');
|
|
493
|
+
error.code = 'MISSING_OPTION';
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
496
|
+
const raw = await fs.readFile(options.file, 'utf8');
|
|
497
|
+
const draft = JSON.parse(raw);
|
|
498
|
+
return {
|
|
499
|
+
method: 'POST',
|
|
500
|
+
url: resolveServiceUrl(baseUrl, '/cli/programs').toString(),
|
|
501
|
+
body: draft
|
|
502
|
+
};
|
|
503
|
+
}
|
|
489
504
|
case 'programs-propose': {
|
|
490
505
|
if (!options.file) {
|
|
491
506
|
const error = new Error('--file is required for programs propose.');
|
|
@@ -632,6 +647,42 @@ export async function buildWriteRequest(commandId, options, sessionState) {
|
|
|
632
647
|
}
|
|
633
648
|
|
|
634
649
|
const remoteWriteCommandHandlers = {
|
|
650
|
+
'programs-create': async (options, sessionState) => {
|
|
651
|
+
const baseUrl = sessionState.session?.transport?.baseUrl;
|
|
652
|
+
if (!baseUrl) throw notImplementedError();
|
|
653
|
+
|
|
654
|
+
const filePath = options.file;
|
|
655
|
+
if (!filePath) {
|
|
656
|
+
const error = new Error('--file is required for programs create.');
|
|
657
|
+
error.code = 'MISSING_OPTION';
|
|
658
|
+
throw error;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
662
|
+
const draft = JSON.parse(raw);
|
|
663
|
+
|
|
664
|
+
const endpoint = resolveServiceUrl(baseUrl, '/cli/programs');
|
|
665
|
+
const response = await fetch(endpoint, {
|
|
666
|
+
method: 'POST',
|
|
667
|
+
headers: {
|
|
668
|
+
'Content-Type': 'application/json',
|
|
669
|
+
Authorization: `Bearer ${sessionState.session?.auth?.accessToken ?? ''}`
|
|
670
|
+
},
|
|
671
|
+
body: JSON.stringify(draft)
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
if (response.status === 401) throw authenticationFailedError();
|
|
675
|
+
if (response.status === 403) await throwForbiddenResponse(response, sessionState, 'programs-create');
|
|
676
|
+
if (!response.ok) {
|
|
677
|
+
const payload = await response.json().catch(() => null);
|
|
678
|
+
const error = new Error(payload?.error ?? `Unexpected error (HTTP ${response.status}).`);
|
|
679
|
+
error.code = 'REMOTE_HTTP_ERROR';
|
|
680
|
+
throw error;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return response.json();
|
|
684
|
+
},
|
|
685
|
+
|
|
635
686
|
'programs-propose': async (options, sessionState) => {
|
|
636
687
|
const baseUrl = sessionState.session?.transport?.baseUrl;
|
|
637
688
|
if (!baseUrl) throw notImplementedError();
|
package/src/score-context.js
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
// They are NOT persisted — pure projections of (current snapshot, previous
|
|
5
5
|
// snapshot) into agent-friendly explanatory shape. See GitHub issue #498.
|
|
6
6
|
|
|
7
|
+
import {
|
|
8
|
+
publicTrainingLanguageViolations,
|
|
9
|
+
replacePublicTrainingLanguageTerms
|
|
10
|
+
} from './training-language.js';
|
|
11
|
+
|
|
7
12
|
// Score bands. Inclusive lower bound, exclusive upper bound (except 'peak').
|
|
8
13
|
// 0..40 weak
|
|
9
14
|
// 40..60 developing
|
|
@@ -29,7 +34,7 @@ export function computeScoreBand(score) {
|
|
|
29
34
|
// Component-keyed action templates surfaced as `recommendedNextActions` for
|
|
30
35
|
// each top-2 negative driver. Keep these short, imperative, single-line.
|
|
31
36
|
const COMPONENT_ACTIONS = {
|
|
32
|
-
coverage: '
|
|
37
|
+
coverage: 'Train the missing muscles before adding more of what is already trained.',
|
|
33
38
|
recovery: 'Go easier next session unless sleep and soreness feel clearly better.',
|
|
34
39
|
stimulus: 'Put the short muscle groups earlier next time.',
|
|
35
40
|
execution: 'Follow the planned work more closely next session.',
|
|
@@ -56,8 +61,12 @@ function actionForDriver(driver) {
|
|
|
56
61
|
|
|
57
62
|
function driverDisplayMessage(driver) {
|
|
58
63
|
if (!driver || typeof driver !== 'object') return null;
|
|
59
|
-
if (typeof driver.message === 'string' && driver.message.trim())
|
|
60
|
-
|
|
64
|
+
if (typeof driver.message === 'string' && driver.message.trim()) {
|
|
65
|
+
return replacePublicTrainingLanguageTerms(driver.message);
|
|
66
|
+
}
|
|
67
|
+
if (typeof driver.label === 'string' && driver.label.trim()) {
|
|
68
|
+
return replacePublicTrainingLanguageTerms(driver.label);
|
|
69
|
+
}
|
|
61
70
|
return null;
|
|
62
71
|
}
|
|
63
72
|
|
|
@@ -152,6 +161,22 @@ export function enrichScoreSnapshots(snapshots) {
|
|
|
152
161
|
});
|
|
153
162
|
}
|
|
154
163
|
|
|
164
|
+
function includeSignalsOption(options = {}) {
|
|
165
|
+
const value = options.includeSignals ?? options['include-signals'];
|
|
166
|
+
return value === true || value === 'true' || value === '1';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function compactScoreHistorySnapshots(snapshots, options = {}) {
|
|
170
|
+
if (!Array.isArray(snapshots)) return [];
|
|
171
|
+
if (includeSignalsOption(options)) return snapshots;
|
|
172
|
+
return snapshots.map((snapshot) => {
|
|
173
|
+
if (!snapshot || typeof snapshot !== 'object') return snapshot;
|
|
174
|
+
const compact = { ...snapshot };
|
|
175
|
+
delete compact.signals;
|
|
176
|
+
return compact;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
155
180
|
export function enrichScoreSnapshot(current, previous) {
|
|
156
181
|
if (!current || typeof current !== 'object') return current;
|
|
157
182
|
|
|
@@ -187,3 +212,33 @@ export function enrichScoreSnapshot(current, previous) {
|
|
|
187
212
|
enriched.summaryText = computeSummaryText(enriched);
|
|
188
213
|
return enriched;
|
|
189
214
|
}
|
|
215
|
+
|
|
216
|
+
export function scoreContextPublicLanguageViolations(enriched) {
|
|
217
|
+
if (!enriched || typeof enriched !== 'object') return [];
|
|
218
|
+
const publicTexts = [];
|
|
219
|
+
if (typeof enriched.summaryText === 'string') {
|
|
220
|
+
publicTexts.push({ field: 'summaryText', text: enriched.summaryText });
|
|
221
|
+
}
|
|
222
|
+
if (Array.isArray(enriched.recommendedNextActions)) {
|
|
223
|
+
enriched.recommendedNextActions.forEach((action, index) => {
|
|
224
|
+
if (typeof action?.driverMessage === 'string') {
|
|
225
|
+
publicTexts.push({
|
|
226
|
+
field: `recommendedNextActions[${index}].driverMessage`,
|
|
227
|
+
text: action.driverMessage
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (typeof action?.action === 'string') {
|
|
231
|
+
publicTexts.push({
|
|
232
|
+
field: `recommendedNextActions[${index}].action`,
|
|
233
|
+
text: action.action
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return publicTexts
|
|
240
|
+
.filter(({ text }) => text.trim())
|
|
241
|
+
.flatMap(({ field, text }) => (
|
|
242
|
+
publicTrainingLanguageViolations(text).map((violation) => ({ ...violation, field }))
|
|
243
|
+
));
|
|
244
|
+
}
|
package/src/summary-evals.js
CHANGED
|
@@ -17,6 +17,7 @@ import { formatIncrementScorePrelude, isScoreQuestion } from './score-prelude.js
|
|
|
17
17
|
import {
|
|
18
18
|
AI_PROMPT_VERSIONS,
|
|
19
19
|
generateAskAnswer,
|
|
20
|
+
generateAskAnswerStarterGraph,
|
|
20
21
|
generateCheckpointSummary,
|
|
21
22
|
generateCoachingSummary,
|
|
22
23
|
generateVitalsSummary,
|
|
@@ -25,7 +26,14 @@ import {
|
|
|
25
26
|
import { computeScoreBand } from './score-context.js';
|
|
26
27
|
import { stripXMLTagBlocks } from './prompt-security.js';
|
|
27
28
|
import { extractAskProgramDraft, hasProgramDraftBlock } from './program-draft.js';
|
|
29
|
+
import { extractCoachAdvice } from './coach-advice.js';
|
|
28
30
|
import { findAskAnswerExerciseMentions, verifyAskAnswer } from './ask-answer-verifier.js';
|
|
31
|
+
import {
|
|
32
|
+
STARTER_GRAPH_VERSION,
|
|
33
|
+
applyStarterPromptStructuredIntentHint,
|
|
34
|
+
starterPromptToolPlan
|
|
35
|
+
} from './ask-starter-prompts.js';
|
|
36
|
+
import { publicTrainingLanguageViolations } from './training-language.js';
|
|
29
37
|
|
|
30
38
|
const __filename = fileURLToPath(import.meta.url);
|
|
31
39
|
const __dirname = path.dirname(__filename);
|
|
@@ -120,8 +128,9 @@ export function buildSummaryEvalContext(snapshot, testCase) {
|
|
|
120
128
|
const question = testCase.context?.question ?? testCase.question ?? '';
|
|
121
129
|
const today = testCase.context?.today ?? testCase.today ?? null;
|
|
122
130
|
const history = Array.isArray(testCase.context?.history) ? testCase.context.history : [];
|
|
131
|
+
const routeToday = today ? new Date(today) : new Date();
|
|
123
132
|
const routed = question
|
|
124
|
-
? askRoutedContext(snapshot, question, { exclude: new Set(testCase.exclude ?? []), history, today:
|
|
133
|
+
? askRoutedContext(snapshot, question, { exclude: new Set(testCase.exclude ?? []), history, today: routeToday })
|
|
125
134
|
: null;
|
|
126
135
|
// Mirror production: the live /cli/ask path prepends the Increment Score
|
|
127
136
|
// prelude to the routed context. Including it here means a live eval feeds
|
|
@@ -132,10 +141,11 @@ export function buildSummaryEvalContext(snapshot, testCase) {
|
|
|
132
141
|
const routedContext = routed?.context ?? null;
|
|
133
142
|
const trainingData = testCase.context?.trainingData
|
|
134
143
|
?? (prelude && routedContext ? `${prelude}\n\n${routedContext}` : (routedContext ?? prelude));
|
|
144
|
+
const routedMetadata = applyStarterPromptEvalMetadata(routed?.metadata ?? null, testCase);
|
|
135
145
|
return {
|
|
136
146
|
...(testCase.context ?? {}),
|
|
137
147
|
trainingData,
|
|
138
|
-
routedMetadata
|
|
148
|
+
routedMetadata
|
|
139
149
|
};
|
|
140
150
|
}
|
|
141
151
|
case 'scoreCommentary':
|
|
@@ -152,25 +162,101 @@ function summaryEvalGenerationMetadata(result) {
|
|
|
152
162
|
model: result.model,
|
|
153
163
|
durationMs: result.durationMs,
|
|
154
164
|
fallback: result.fallback,
|
|
165
|
+
promptSurface: result.promptSurface,
|
|
166
|
+
promptVersion: result.promptVersion,
|
|
167
|
+
graphVersion: result.graph?.version,
|
|
168
|
+
starterPromptFamily: result.graph?.family,
|
|
169
|
+
graphNodes: result.graph?.nodes,
|
|
170
|
+
graphToolNames: result.graph?.toolNames,
|
|
155
171
|
langfuseTraceId: result.langfuseTraceId,
|
|
156
172
|
langfuseObservationId: result.langfuseObservationId
|
|
157
173
|
}).filter(([, value]) => value !== undefined && value !== null)
|
|
158
174
|
);
|
|
159
175
|
}
|
|
160
176
|
|
|
177
|
+
function evalEvidenceLabel(section, toolName) {
|
|
178
|
+
const cleaned = String(section || toolName || 'evidence')
|
|
179
|
+
.replace(/^observation_/, '')
|
|
180
|
+
.replace(/_/g, ' ')
|
|
181
|
+
.trim();
|
|
182
|
+
return cleaned ? cleaned.replace(/\b\w/g, (char) => char.toUpperCase()) : 'Evidence';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function applyStarterPromptEvalMetadata(metadata, testCase) {
|
|
186
|
+
const starterPrompt = testCase.starterPrompt;
|
|
187
|
+
if (!starterPrompt?.family || !metadata || typeof metadata !== 'object') return metadata;
|
|
188
|
+
|
|
189
|
+
const hintedMetadata = applyStarterPromptStructuredIntentHint(metadata, starterPrompt);
|
|
190
|
+
const plannedCalls = starterPromptToolPlan(starterPrompt);
|
|
191
|
+
if (plannedCalls.length === 0) return metadata;
|
|
192
|
+
|
|
193
|
+
const names = plannedCalls.map((call) => call.name).filter(Boolean);
|
|
194
|
+
const toolParams = Object.fromEntries(plannedCalls.map((call) => [call.name, call.params ?? {}]));
|
|
195
|
+
const provenanceEntries = plannedCalls.map((call) => ({
|
|
196
|
+
section: call.name,
|
|
197
|
+
toolName: call.name,
|
|
198
|
+
sourceIds: []
|
|
199
|
+
}));
|
|
200
|
+
const contextBundle = hintedMetadata.contextBundle && typeof hintedMetadata.contextBundle === 'object'
|
|
201
|
+
? hintedMetadata.contextBundle
|
|
202
|
+
: null;
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
...hintedMetadata,
|
|
206
|
+
starterPromptFamily: starterPrompt.family,
|
|
207
|
+
graphVersion: STARTER_GRAPH_VERSION,
|
|
208
|
+
graphNodes: ['prepare_evidence', 'synthesize'],
|
|
209
|
+
graphToolNames: names,
|
|
210
|
+
toolsUsed: [...new Set([...(hintedMetadata.toolsUsed ?? []), ...names])],
|
|
211
|
+
toolParams: {
|
|
212
|
+
...(hintedMetadata.toolParams ?? {}),
|
|
213
|
+
...toolParams
|
|
214
|
+
},
|
|
215
|
+
evidencePlan: hintedMetadata.evidencePlan && typeof hintedMetadata.evidencePlan === 'object'
|
|
216
|
+
? {
|
|
217
|
+
...hintedMetadata.evidencePlan,
|
|
218
|
+
executedTools: [...new Set([...(hintedMetadata.evidencePlan.executedTools ?? []), ...names])]
|
|
219
|
+
}
|
|
220
|
+
: hintedMetadata.evidencePlan,
|
|
221
|
+
provenance: [...(hintedMetadata.provenance ?? []), ...provenanceEntries],
|
|
222
|
+
...(contextBundle
|
|
223
|
+
? {
|
|
224
|
+
contextBundle: {
|
|
225
|
+
...contextBundle,
|
|
226
|
+
executedTools: [...new Set([...(contextBundle.executedTools ?? []), ...names])],
|
|
227
|
+
evidenceUsed: [
|
|
228
|
+
...(contextBundle.evidenceUsed ?? []),
|
|
229
|
+
...provenanceEntries.map((entry) => ({
|
|
230
|
+
label: evalEvidenceLabel(entry.section, entry.toolName),
|
|
231
|
+
section: entry.section,
|
|
232
|
+
toolName: entry.toolName,
|
|
233
|
+
sourceTimestamp: null,
|
|
234
|
+
sourceIds: [],
|
|
235
|
+
noteSourceIds: [],
|
|
236
|
+
missingDataFlags: []
|
|
237
|
+
}))
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
: {})
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
161
245
|
function buildAskEvalStructuredMetadata(testCase, context, output) {
|
|
162
246
|
if (testCase.surface !== 'ask') return {};
|
|
163
247
|
const parsedAsk = extractAskProgramDraft(output, {
|
|
164
248
|
canonicalizeExerciseName: canonicalExerciseName
|
|
165
249
|
});
|
|
166
|
-
const
|
|
250
|
+
const adviceParsed = extractCoachAdvice(parsedAsk.answerText);
|
|
251
|
+
const answer = stripXMLTagBlocks(adviceParsed.answerText);
|
|
167
252
|
const question = context?.question ?? testCase.context?.question ?? testCase.question ?? '';
|
|
168
253
|
const routingMetadata = context?.routedMetadata ?? null;
|
|
169
254
|
return {
|
|
170
255
|
routingMetadata,
|
|
171
256
|
structured: buildAskStructuredResponse(answer, routingMetadata ?? {}, {
|
|
172
257
|
programDraft: askStructuredProgramDraft(parsedAsk, routingMetadata),
|
|
173
|
-
question
|
|
258
|
+
question,
|
|
259
|
+
coachAdvice: adviceParsed.coachAdvice ?? testCase.coachAdviceCandidate ?? null
|
|
174
260
|
})
|
|
175
261
|
};
|
|
176
262
|
}
|
|
@@ -215,13 +301,24 @@ export async function generateSummaryEvalOutputWithMetadata(testCase, context, s
|
|
|
215
301
|
const trainingContext = context.trainingData
|
|
216
302
|
?? askRoutedContext(snapshot, question, { exclude: new Set(testCase.exclude ?? []) }).context
|
|
217
303
|
?? askContext(snapshot, { exclude: new Set(testCase.exclude ?? []) });
|
|
218
|
-
result =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
304
|
+
result = testCase.starterPrompt?.family
|
|
305
|
+
? await generateAskAnswerStarterGraph(trainingContext, question, {
|
|
306
|
+
apiKey,
|
|
307
|
+
history: context.history ?? [],
|
|
308
|
+
tone: context.tone,
|
|
309
|
+
model: context.model,
|
|
310
|
+
snapshot,
|
|
311
|
+
starterPrompt: testCase.starterPrompt,
|
|
312
|
+
routingMetadata: context.routedMetadata ?? undefined,
|
|
313
|
+
exclude: testCase.exclude ?? []
|
|
314
|
+
})
|
|
315
|
+
: await generateAskAnswer(trainingContext, question, {
|
|
316
|
+
apiKey,
|
|
317
|
+
history: context.history ?? [],
|
|
318
|
+
tone: context.tone,
|
|
319
|
+
model: context.model,
|
|
320
|
+
routingMetadata: context.routedMetadata ?? undefined
|
|
321
|
+
});
|
|
225
322
|
break;
|
|
226
323
|
}
|
|
227
324
|
default:
|
|
@@ -1185,7 +1282,10 @@ function hasUnqualifiedDeclineLanguage(window) {
|
|
|
1185
1282
|
const text = normalizeText(window);
|
|
1186
1283
|
const decline = /\b(drop(?:ped|ping|s)?(?: off)?|drop-off|declin(?:e|ed|ing)|regress(?:ed|ion|ing)?|fell|fall(?:ing)?|decreas(?:e|ed|ing)|lower|worse|slid|slipped)\b/i;
|
|
1187
1284
|
if (!decline.test(text)) return false;
|
|
1188
|
-
if (/\b(?:
|
|
1285
|
+
if (/\b(?:volume|tonnage|work rate|work done|weekly work)\b.{0,80}\b(drop(?:ped|ping|s)?(?: off)?|drop-off|declin(?:e|ed|ing)?|decreas(?:e|ed|ing)?|fell|fall(?:ing)?|lower|down|dip(?:ped)?|pullback)\b/i.test(text)) return false;
|
|
1286
|
+
if (/\b(drop(?:ped|ping|s)?(?: off)?|drop-off|declin(?:e|ed|ing)?|decreas(?:e|ed|ing)?|fell|fall(?:ing)?|lower|down|dip(?:ped)?|pullback)\b.{0,80}\b(?:volume|tonnage|work rate|work done|weekly work)\b/i.test(text)) return false;
|
|
1287
|
+
if (/\b(?:no|not|isn'?t|wasn'?t|weren'?t|doesn'?t|don'?t|didn'?t|without|rather than)\b.{0,60}\b(drop(?:ped|ping|s)?(?: off)?|drop-off|declin(?:e|ed|ing)?|decreas(?:e|ed|ing)?|regress(?:ed|ion|ing)?|fall(?:ing)?|fell|lower|worse|slid|slipped)\b/i.test(text)) return false;
|
|
1288
|
+
if (/\b(drop(?:ped|ping|s)?(?: off)?|drop-off|declin(?:e|ed|ing)?|decreas(?:e|ed|ing)?|regress(?:ed|ion|ing)?|fall(?:ing)?|fell|lower|worse|slid|slipped)\b.{0,60}\b(?:no|not|isn'?t|wasn'?t|weren'?t|doesn'?t|don'?t|didn'?t)\b/i.test(text)) return false;
|
|
1189
1289
|
if (/\b(?:rep|reps)\b.{0,20}\b(drop(?:ped|ping|s)?(?: off)?|drop-off|slip(?:ped|ping)?|fell|fall(?:ing)?|lower|declin(?:e|ed|ing)?|decreas(?:e|ed|ing)?|worse)\b/i.test(text)) return false;
|
|
1190
1290
|
if (/\b(drop(?:ped|ping|s)?(?: off)?|drop-off|slip(?:ped|ping)?|fell|fall(?:ing)?|lower|declin(?:e|ed|ing)?|decreas(?:e|ed|ing)?|worse)\b.{0,20}\b(?:rep|reps)\b/i.test(text)) return false;
|
|
1191
1291
|
return true;
|
|
@@ -1338,13 +1438,26 @@ export function evaluateAskScoreVoice(output, testCase) {
|
|
|
1338
1438
|
hits.add('day-over-day delta number');
|
|
1339
1439
|
}
|
|
1340
1440
|
|
|
1341
|
-
|
|
1441
|
+
const rawScoreResult = {
|
|
1342
1442
|
key: 'ask_score_voice',
|
|
1343
1443
|
passed: hits.size === 0,
|
|
1344
1444
|
reason: hits.size === 0
|
|
1345
1445
|
? 'Ask answer does not recite raw Increment Score component sub-scores.'
|
|
1346
1446
|
: `Ask answer recites raw score internals: ${[...hits].join(', ')}. Speak in training reality, not raw sub-scores.`
|
|
1347
1447
|
};
|
|
1448
|
+
|
|
1449
|
+
if (!rawScoreResult.passed) return rawScoreResult;
|
|
1450
|
+
|
|
1451
|
+
const publicLanguageHits = publicTrainingLanguageViolations(text).map((violation) => violation.term);
|
|
1452
|
+
if (publicLanguageHits.length > 0) {
|
|
1453
|
+
return {
|
|
1454
|
+
key: 'ask_score_voice',
|
|
1455
|
+
passed: false,
|
|
1456
|
+
reason: `Ask answer uses internal training language: ${publicLanguageHits.join(', ')}. Speak in normal training language.`
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
return rawScoreResult;
|
|
1348
1461
|
}
|
|
1349
1462
|
|
|
1350
1463
|
function relevantSessionsForStaleness(snapshot, testCase) {
|
|
@@ -1591,8 +1704,10 @@ function outputCallsStaleEvidenceRecent(outputText, row) {
|
|
|
1591
1704
|
const normalized = normalizeText(outputText);
|
|
1592
1705
|
const claimsRecent = /\brecent(?:ly)?\b/i.test(normalized);
|
|
1593
1706
|
if (!claimsRecent) return false;
|
|
1594
|
-
const explicitlyNotRecent = /\b(?:not|isn'?t|wasn'?t|no longer)\s+(
|
|
1595
|
-
|| /\
|
|
1707
|
+
const explicitlyNotRecent = /\b(?:not|isn'?t|wasn'?t|no longer)\s+(?:\w+\s+){0,4}?recent\b/i.test(normalized)
|
|
1708
|
+
|| /\b(?:no|not enough|without|missing|lack(?:ing)?|insufficient|don'?t have enough|do not have enough)\s+(?:\w+\s+){0,4}?recent\s+(?:work|sessions?|data|logs?|evidence|history)\b/i.test(normalized)
|
|
1709
|
+
|| /\brecent\b.{0,45}\b(?:not|isn'?t|wasn'?t|stale|old|older|latest logged|last logged)\b/i.test(normalized)
|
|
1710
|
+
|| /\b(?:only|latest|last)\s+logged\b.{0,80}\b(?:stale|old|older|days?\s+ago)\b/i.test(normalized);
|
|
1596
1711
|
if (explicitlyNotRecent) return false;
|
|
1597
1712
|
const daysAgo = Number(row?.daysAgo);
|
|
1598
1713
|
return !Number.isFinite(daysAgo) || !new RegExp(`\\b${daysAgo}\\s+days?\\s+ago\\b`, 'i').test(normalized);
|
|
@@ -1746,12 +1861,19 @@ function evaluateAskEvidencePlan(_output, context, testCase) {
|
|
|
1746
1861
|
}
|
|
1747
1862
|
}
|
|
1748
1863
|
|
|
1749
|
-
for (const key of ['requiredTools', 'optionalTools', 'executedTools', 'evidenceGaps']) {
|
|
1864
|
+
for (const key of ['requiredTools', 'optionalTools', 'executedTools', 'executedDynamicTools', 'evidenceGaps']) {
|
|
1750
1865
|
if (Array.isArray(expected[key]) && !arrayEquals(plan[key], expected[key])) {
|
|
1751
1866
|
failures.push(`Expected evidencePlan.${key} to equal ${expected[key].join(', ')}; got ${(plan[key] ?? []).join(', ')}.`);
|
|
1752
1867
|
}
|
|
1753
1868
|
}
|
|
1754
1869
|
|
|
1870
|
+
if (Array.isArray(expected.dynamicTools)) {
|
|
1871
|
+
const actualDynamicTools = (plan.dynamicTools ?? []).map((item) => item.toolName);
|
|
1872
|
+
if (!arrayEquals(actualDynamicTools, expected.dynamicTools)) {
|
|
1873
|
+
failures.push(`Expected evidencePlan.dynamicTools to equal ${expected.dynamicTools.join(', ')}; got ${actualDynamicTools.join(', ')}.`);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1755
1877
|
if (Array.isArray(expected.excludedExecutedTools)) {
|
|
1756
1878
|
const executed = new Set(plan.executedTools ?? []);
|
|
1757
1879
|
const hits = expected.excludedExecutedTools.filter((toolName) => executed.has(toolName));
|
|
@@ -1955,6 +2077,35 @@ function evaluateAskStructuredResponse(_output, context, testCase, structured) {
|
|
|
1955
2077
|
failures.push(`Expected programDraft present=${expected.programDraftPresent}, got ${hasProgramDraft}.`);
|
|
1956
2078
|
}
|
|
1957
2079
|
}
|
|
2080
|
+
|
|
2081
|
+
if (typeof expected.coachAdvicePresent === 'boolean') {
|
|
2082
|
+
const hasCoachAdvice = structured.coachAdvice != null;
|
|
2083
|
+
if (hasCoachAdvice !== expected.coachAdvicePresent) {
|
|
2084
|
+
failures.push(`Expected coachAdvice present=${expected.coachAdvicePresent}, got ${hasCoachAdvice}.`);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
if (expected.coachAdviceKind && structured.coachAdvice?.kind !== expected.coachAdviceKind) {
|
|
2089
|
+
failures.push(`Expected coachAdvice kind ${expected.coachAdviceKind}, got ${structured.coachAdvice?.kind ?? 'null'}.`);
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
if (expected.coachAdviceTarget && structured.coachAdvice?.target !== expected.coachAdviceTarget) {
|
|
2093
|
+
failures.push(`Expected coachAdvice target ${expected.coachAdviceTarget}, got ${structured.coachAdvice?.target ?? 'null'}.`);
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
requireStructuredStrings(
|
|
2097
|
+
structured.coachAdvice?.evidenceRefs,
|
|
2098
|
+
expected.requiredCoachAdviceEvidenceRefs,
|
|
2099
|
+
'coachAdvice evidence ref(s)',
|
|
2100
|
+
failures
|
|
2101
|
+
);
|
|
2102
|
+
|
|
2103
|
+
requireStructuredStrings(
|
|
2104
|
+
structured.coachAdvice?.outcomeCriteria,
|
|
2105
|
+
expected.requiredCoachAdviceOutcomeCriteria,
|
|
2106
|
+
'coachAdvice outcome criteria',
|
|
2107
|
+
failures
|
|
2108
|
+
);
|
|
1958
2109
|
}
|
|
1959
2110
|
|
|
1960
2111
|
return {
|
|
@@ -2278,42 +2429,47 @@ export function evaluateSummaryOutputFromSnapshot(testCase, snapshot, output) {
|
|
|
2278
2429
|
? extractAskProgramDraft(output, { canonicalizeExerciseName: canonicalExerciseName })
|
|
2279
2430
|
: null;
|
|
2280
2431
|
const visibleOutput = parsedAsk
|
|
2281
|
-
?
|
|
2432
|
+
? parsedAsk.answerText
|
|
2282
2433
|
: output;
|
|
2434
|
+
const adviceParsed = testCase.surface === 'ask'
|
|
2435
|
+
? extractCoachAdvice(visibleOutput)
|
|
2436
|
+
: { answerText: visibleOutput, coachAdvice: null };
|
|
2437
|
+
const finalVisibleOutput = stripXMLTagBlocks(adviceParsed.answerText);
|
|
2283
2438
|
const structuredAsk = testCase.surface === 'ask'
|
|
2284
|
-
? buildAskStructuredResponse(
|
|
2439
|
+
? buildAskStructuredResponse(finalVisibleOutput, context.routedMetadata ?? {}, {
|
|
2285
2440
|
programDraft: askStructuredProgramDraft(structuredParsedAsk, context.routedMetadata),
|
|
2286
|
-
question: context.question ?? testCase.context?.question ?? testCase.question ?? ''
|
|
2441
|
+
question: context.question ?? testCase.context?.question ?? testCase.question ?? '',
|
|
2442
|
+
coachAdvice: adviceParsed.coachAdvice ?? testCase.coachAdviceCandidate ?? null
|
|
2287
2443
|
})
|
|
2288
2444
|
: null;
|
|
2289
2445
|
|
|
2290
2446
|
const checks = [
|
|
2291
|
-
evaluateNoInsight(
|
|
2292
|
-
evaluateShape(
|
|
2293
|
-
evaluateRequiredMentions(
|
|
2294
|
-
evaluateAnyOfMentions(
|
|
2295
|
-
evaluateForbiddenPhrases(
|
|
2296
|
-
evaluateForbiddenMentions(
|
|
2297
|
-
evaluateExerciseMentions(
|
|
2298
|
-
evaluateWorkoutClaims(
|
|
2299
|
-
evaluateAskClaims(
|
|
2300
|
-
evaluateAskDirectionalConsistency(
|
|
2301
|
-
evaluateAskScoreVoice(
|
|
2302
|
-
evaluateAskSelfReference(
|
|
2303
|
-
evaluateAskVolunteeredScore(
|
|
2304
|
-
evaluateAskStaleness(
|
|
2305
|
-
evaluateAskToolProvenance(
|
|
2306
|
-
evaluateFormulaVersion(
|
|
2307
|
-
evaluateAskEvidencePlan(
|
|
2308
|
-
evaluateAskMetadata(
|
|
2309
|
-
evaluateAskStructuredResponse(
|
|
2310
|
-
evaluateScoreCommentaryAction(
|
|
2311
|
-
evaluateScoreCommentarySynthesis(
|
|
2312
|
-
evaluateScoreCommentaryExerciseInvention(
|
|
2313
|
-
evaluateScoreCommentaryBand(
|
|
2314
|
-
evaluateScoreCommentaryTone(
|
|
2315
|
-
evaluateScoreCommentaryLength(
|
|
2316
|
-
evaluatePersonaMotivation(
|
|
2447
|
+
evaluateNoInsight(finalVisibleOutput, testCase),
|
|
2448
|
+
evaluateShape(finalVisibleOutput, testCase, context),
|
|
2449
|
+
evaluateRequiredMentions(finalVisibleOutput, testCase),
|
|
2450
|
+
evaluateAnyOfMentions(finalVisibleOutput, testCase),
|
|
2451
|
+
evaluateForbiddenPhrases(finalVisibleOutput, testCase),
|
|
2452
|
+
evaluateForbiddenMentions(finalVisibleOutput, testCase),
|
|
2453
|
+
evaluateExerciseMentions(finalVisibleOutput, snapshot, context, testCase.surface, testCase),
|
|
2454
|
+
evaluateWorkoutClaims(finalVisibleOutput, context, testCase),
|
|
2455
|
+
evaluateAskClaims(finalVisibleOutput, snapshot, testCase, context),
|
|
2456
|
+
evaluateAskDirectionalConsistency(finalVisibleOutput, snapshot, testCase),
|
|
2457
|
+
evaluateAskScoreVoice(finalVisibleOutput, testCase),
|
|
2458
|
+
evaluateAskSelfReference(finalVisibleOutput, testCase),
|
|
2459
|
+
evaluateAskVolunteeredScore(finalVisibleOutput, testCase, context),
|
|
2460
|
+
evaluateAskStaleness(finalVisibleOutput, snapshot, testCase),
|
|
2461
|
+
evaluateAskToolProvenance(finalVisibleOutput, context, testCase, snapshot),
|
|
2462
|
+
evaluateFormulaVersion(finalVisibleOutput, snapshot, testCase),
|
|
2463
|
+
evaluateAskEvidencePlan(finalVisibleOutput, context, testCase),
|
|
2464
|
+
evaluateAskMetadata(finalVisibleOutput, context, testCase),
|
|
2465
|
+
evaluateAskStructuredResponse(finalVisibleOutput, context, testCase, structuredAsk),
|
|
2466
|
+
evaluateScoreCommentaryAction(finalVisibleOutput, context, testCase),
|
|
2467
|
+
evaluateScoreCommentarySynthesis(finalVisibleOutput, context, testCase),
|
|
2468
|
+
evaluateScoreCommentaryExerciseInvention(finalVisibleOutput, snapshot, context, testCase),
|
|
2469
|
+
evaluateScoreCommentaryBand(finalVisibleOutput, context, testCase),
|
|
2470
|
+
evaluateScoreCommentaryTone(finalVisibleOutput, testCase),
|
|
2471
|
+
evaluateScoreCommentaryLength(finalVisibleOutput, testCase),
|
|
2472
|
+
evaluatePersonaMotivation(finalVisibleOutput, context, testCase),
|
|
2317
2473
|
evaluateProgramDraft(output, testCase, parsedAsk)
|
|
2318
2474
|
];
|
|
2319
2475
|
|