incremnt 0.8.7 → 0.8.9

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 +50 -13
  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 +161 -3
  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,97 @@
1
+ {
2
+ "version": 1,
3
+ "approvedTerms": [
4
+ "Training",
5
+ "Progress",
6
+ "Recovery",
7
+ "Muscles",
8
+ "Plan",
9
+ "Driver",
10
+ "Workout",
11
+ "Rest",
12
+ "Effort"
13
+ ],
14
+ "bannedPublicPhrases": [
15
+ {
16
+ "term": "muscle work",
17
+ "replacement": "muscles trained",
18
+ "rationale": "Avoid internal score wording for muscle group training."
19
+ },
20
+ {
21
+ "term": "muscle coverage",
22
+ "replacement": "muscles trained",
23
+ "rationale": "Coverage is a score-engine concept, not user-facing coaching language."
24
+ },
25
+ {
26
+ "term": "scored workouts",
27
+ "replacement": "workouts",
28
+ "rationale": "Users log workouts; scoring eligibility is internal."
29
+ },
30
+ {
31
+ "term": "strength work",
32
+ "replacement": "training",
33
+ "rationale": "Use broad training language unless a strength-specific set is being described."
34
+ },
35
+ {
36
+ "term": "health signals",
37
+ "replacement": "sleep, heart rate, HRV, soreness, and recent training",
38
+ "rationale": "Name the concrete recovery inputs instead of an analytics abstraction."
39
+ },
40
+ {
41
+ "term": "training signal",
42
+ "replacement": "recent training",
43
+ "rationale": "Signal is an internal data-quality term."
44
+ },
45
+ {
46
+ "term": "readiness signal",
47
+ "replacement": "recovery data",
48
+ "rationale": "Signal is an internal data-quality term."
49
+ },
50
+ {
51
+ "term": "observation window",
52
+ "replacement": "recent period",
53
+ "rationale": "Observation window is an implementation detail."
54
+ },
55
+ {
56
+ "term": "weighted drag",
57
+ "replacement": "main limiter",
58
+ "rationale": "Weighted scoring math should not leak to users."
59
+ },
60
+ {
61
+ "term": "weighted points",
62
+ "replacement": "points",
63
+ "rationale": "Weighted scoring math should not leak to users."
64
+ }
65
+ ],
66
+ "bannedPublicWords": [
67
+ {
68
+ "term": "stimulus",
69
+ "replacement": "training",
70
+ "rationale": "Stimulus is score-engine vocabulary."
71
+ },
72
+ {
73
+ "term": "coverage",
74
+ "replacement": "muscles trained",
75
+ "rationale": "Coverage is score-engine vocabulary."
76
+ },
77
+ {
78
+ "term": "adherence",
79
+ "replacement": "following the plan",
80
+ "rationale": "Adherence sounds clinical and dashboard-like."
81
+ },
82
+ {
83
+ "term": "component",
84
+ "replacement": "driver",
85
+ "rationale": "Component is implementation structure; Driver is approved public language."
86
+ },
87
+ {
88
+ "term": "signal",
89
+ "replacement": "data",
90
+ "rationale": "Signal is internal analytics language."
91
+ }
92
+ ],
93
+ "notes": [
94
+ "Internal enum, schema, database, and fixture terms may keep existing names when they are not user-facing.",
95
+ "Driver is approved as the public word for why a score or recommendation moved."
96
+ ]
97
+ }
@@ -0,0 +1,94 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import path from 'node:path';
4
+
5
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
6
+ const contractPath = path.join(moduleDir, 'training-language-public-terms.json');
7
+
8
+ let cachedContract = null;
9
+
10
+ function loadContractFromDisk() {
11
+ const raw = readFileSync(contractPath, 'utf8');
12
+ return JSON.parse(raw);
13
+ }
14
+
15
+ export function loadPublicTrainingLanguageContract() {
16
+ if (!cachedContract) {
17
+ cachedContract = loadContractFromDisk();
18
+ }
19
+ return cachedContract;
20
+ }
21
+
22
+ function escapeRegExp(value) {
23
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
24
+ }
25
+
26
+ function boundaryPattern(term) {
27
+ const escaped = escapeRegExp(term).replace(/\s+/g, '\\s+');
28
+ return new RegExp(`(?<![\\p{L}\\p{N}_-])${escaped}(?![\\p{L}\\p{N}_-])`, 'giu');
29
+ }
30
+
31
+ function normalizeRule(rule, kind) {
32
+ if (typeof rule === 'string') {
33
+ return { kind, term: rule, replacement: null, rationale: null };
34
+ }
35
+ return {
36
+ kind,
37
+ term: rule.term,
38
+ replacement: rule.replacement ?? null,
39
+ rationale: rule.rationale ?? null
40
+ };
41
+ }
42
+
43
+ export function publicTrainingLanguageViolations(text, options = {}) {
44
+ if (typeof text !== 'string' || text.length === 0) return [];
45
+ const contract = options.contract ?? loadPublicTrainingLanguageContract();
46
+ const rules = [
47
+ ...(contract.bannedPublicPhrases ?? []).map((rule) => normalizeRule(rule, 'phrase')),
48
+ ...(contract.bannedPublicWords ?? []).map((rule) => normalizeRule(rule, 'word'))
49
+ ];
50
+
51
+ const violations = [];
52
+ for (const rule of rules) {
53
+ if (!rule.term) continue;
54
+ const pattern = boundaryPattern(rule.term);
55
+ const matches = text.matchAll(pattern);
56
+ for (const match of matches) {
57
+ violations.push({
58
+ kind: rule.kind,
59
+ term: rule.term,
60
+ match: match[0],
61
+ index: match.index,
62
+ replacement: rule.replacement,
63
+ rationale: rule.rationale
64
+ });
65
+ }
66
+ }
67
+ return violations;
68
+ }
69
+
70
+ export function assertPublicTrainingLanguage(text, options = {}) {
71
+ const violations = publicTrainingLanguageViolations(text, options);
72
+ if (violations.length > 0) {
73
+ const terms = violations.map((violation) => violation.term).join(', ');
74
+ throw new Error(`Public training language violation: ${terms}`);
75
+ }
76
+ return true;
77
+ }
78
+
79
+ export function replacePublicTrainingLanguageTerms(text, options = {}) {
80
+ if (typeof text !== 'string' || text.length === 0) return text;
81
+ const contract = options.contract ?? loadPublicTrainingLanguageContract();
82
+ const rules = [
83
+ ...(contract.bannedPublicPhrases ?? []).map((rule) => normalizeRule(rule, 'phrase')),
84
+ ...(contract.bannedPublicWords ?? []).map((rule) => normalizeRule(rule, 'word'))
85
+ ]
86
+ .filter((rule) => rule.term && rule.replacement)
87
+ .sort((a, b) => b.term.length - a.term.length);
88
+
89
+ let output = text;
90
+ for (const rule of rules) {
91
+ output = output.replace(boundaryPattern(rule.term), rule.replacement);
92
+ }
93
+ return output;
94
+ }
package/src/transport.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { readSnapshot, resolveSnapshotSource } from './local.js';
2
2
  import { createRemoteTransport } from './remote.js';
3
3
  import { askMissingObservationFollowUpContext, askObservationFollowUpContext, askRoutedContext } from './ask-coach.js';
4
+ import { contractVersion } from './contract.js';
4
5
  import { sanitizeHistory } from './prompt-security.js';
5
6
  import { executeCoachReadTool, executeReadCommand, shouldKeepCurrentCoachObservation } from './queries.js';
6
7
  import { isSessionExpired } from './state.js';
7
- import { fetchRemoteContract, refreshRemoteSession } from './auth.js';
8
+ import { fetchRemoteContract, refreshRemoteContractMetadata, refreshRemoteSession } from './auth.js';
8
9
  import { resolveConfiguredBaseUrl } from './service-url.js';
9
10
 
10
11
  function prefersLocal(options) {
@@ -167,6 +168,11 @@ export async function createTransport(options, sessionState) {
167
168
  }
168
169
  }
169
170
 
171
+ if (!options['dry-run'] && !expired && baseUrl && session.auth?.accessToken && Number(session.transport?.contractVersion) !== contractVersion) {
172
+ const refreshed = await refreshRemoteContractMetadata(baseUrl, session.auth.accessToken, session);
173
+ session = refreshed.session;
174
+ }
175
+
170
176
  return {
171
177
  ...createRemoteTransport({
172
178
  ...sessionState,
package/src/validate.js CHANGED
@@ -40,6 +40,16 @@ export function validateOption(opt, rawValue) {
40
40
 
41
41
  const format = opt.format ?? (opt.type === 'number' ? 'number' : 'text');
42
42
 
43
+ if (opt.type === 'boolean') {
44
+ if (rawValue === true || rawValue === false) return rawValue;
45
+ if (typeof rawValue === 'string') {
46
+ const normalized = rawValue.trim().toLowerCase();
47
+ if (['true', '1', 'yes'].includes(normalized)) return true;
48
+ if (['false', '0', 'no'].includes(normalized)) return false;
49
+ }
50
+ throw new ValidationError(`--${opt.name} must be a boolean flag`, { option: opt.name, format: 'boolean' });
51
+ }
52
+
43
53
  if (rawValue === null) {
44
54
  throw new ValidationError(`--${opt.name} must not be null`, { option: opt.name, format, code: 'MISSING_OPTION' });
45
55
  }
@@ -129,7 +139,7 @@ export function validateOptions(commandEntry, options) {
129
139
  const out = { ...options };
130
140
  for (const opt of commandEntry.options) {
131
141
  if (out[opt.name] === undefined) continue;
132
- if (out[opt.name] === true) {
142
+ if (out[opt.name] === true && opt.type !== 'boolean') {
133
143
  const format = opt.format ?? (opt.type === 'number' ? 'number' : 'text');
134
144
  throw new ValidationError(`--${opt.name} requires a value`, { option: opt.name, format, code: 'MISSING_OPTION' });
135
145
  }