vibeostheog 0.20.7 → 0.20.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.20.9
2
+ - feat: hard-block console.log/debug/info in eslint (warn->error)
3
+ - fix: update constants.js OPUS_DISABLE to 1e-10 (dead code, matches TS source)
4
+ - fix: modelCostPerTurn returns FREE_MODEL_TURN_USD for unknown models
5
+ - docs: rename blackbox to VibeBoX and document local fallback
6
+ - docs: dopamine-style README reformat with OPUS->SONNET->HAIKU pricing
7
+
8
+
1
9
  ## 0.20.7
2
10
  - fix: ship compiled OpenCode plugin bundle
3
11
  - fix: always show model label in tool.execute.after footer, even with zero savings
package/README.md CHANGED
@@ -1,18 +1,87 @@
1
1
  # vibeOS for OpenCode
2
2
 
3
- > **Alpha Omega Launch** - This release is the first major public launch of vibeOS. See [CHANGELOG.md](CHANGELOG.md) for release notes.
3
+ **Prices validated: May 28, 2026** verified against OpenRouter `/api/v1/models`
4
4
 
5
- vibeOS is the cost-aware control plane for OpenCode Desktop. It helps individuals and teams keep expensive models focused on strategy, move implementation work to cheaper tiers, and make the resulting savings visible in real time through the live footer and dashboard.
5
+ Cost-aware control plane for OpenCode Desktop. Keeps expensive models on strategy, routes implementation to cheaper tiers, surfaces savings in real time.
6
6
 
7
7
  For teams, vibeOS adds practical guardrails: delegation enforcement, flow and TDD controls, pattern learning, stress-aware routing, VibeBoX decision tracking, reporting, and remote API protection for the core algorithms.
8
8
 
9
- ## What We Offer
9
+ ## Delegation Enforcement
10
+
11
+ Every `write`/`edit`/`notebookedit` on the **brain tier** is intercepted, cost-estimated, and blocked with a visible enforcement note. Work must be delegated to **medium** or **cheap**. This is the primary savings mechanism.
12
+
13
+ ### Per-Turn Cost (700 input + 300 output tokens)
14
+
15
+ | Tier | Model | Per Turn | Per 100 Turns | vs Opus |
16
+ |------|-------|----------|---------------|---------|
17
+ | Brain | `claude-opus-4-7` | **$0.0330** | **$3.30** | — |
18
+ | Medium | `claude-sonnet-4-6` | **$0.0066** | **$0.66** | saves 80% |
19
+ | Cheap | `claude-haiku-4-5` | **$0.0022** | **$0.22** | saves 93% |
20
+
21
+ *Source: `src/lib/pricing.ts:279-285`. Conservative estimates — actual OpenRouter live: Opus $0.011, Sonnet $0.0066, Haiku $0.0022 per turn. The plugin over-estimates brain cost so savings are always understated.*
22
+
23
+ ### Savings per Delegation
24
+
25
+ | Move | Per Turn | 10x | 100x | 1,000x |
26
+ |------|----------|-----|------|--------|
27
+ | Opus → Haiku | $0.0308 | $0.31 | $3.08 | $30.80 |
28
+ | Opus → Sonnet | $0.0264 | $0.26 | $2.64 | $26.40 |
29
+ | Sonnet → Haiku | $0.0044 | $0.04 | $0.44 | $4.40 |
30
+
31
+ Every blocked brain-tier write/edit saves at least $0.026 (Opus→Sonnet). The running total is tracked in `~/.claude/delegation-state.json` and displayed in the live footer.
32
+
33
+ ---
34
+
35
+ ## Features
36
+
37
+ | Feature | What it does |
38
+ |---------|-------------|
39
+ | **Delegation enforcement** | Blocks write/edit on brain tier. Routes to medium or cheap. |
40
+ | **Live savings footer** | Model, provider, cumulative savings, cache savings, stress gauge, lock/enforcement tags. |
41
+ | **Web dashboard** | SolidJS SPA with SSE real-time push. Model split, savings, session history, trinity controls. |
42
+ | **Trinity runtime** | `trinity set brain\|medium\|cheap`. Switch tiers mid-session. Change optimization mode. |
43
+ | **Flow enforcer** | Pattern-rule checks on write/edit. Extracts TODO/FIXME into an append-only queue. |
44
+ | **TDD enforcer** | Auto-creates test skeletons for changed source. Strict mode: TODO tests fail. |
45
+ | **Pattern learner** | Tracks recurring struggle/routine patterns per project. |
46
+ | **VibeBoX** | 7 sub-regimes, 11 features per turn, 4 loop intervention levels, PIVOT/SWITCH detection. Auto-mode maps regime to optimization mode. |
47
+ | **Stress-aware routing** | Stress gauge in footer. Stress > 1.5 escalates to quality mode. |
48
+ | **Cache savings** | Separate `cache_savings_usd` tracking for scratchpad cache hits. |
49
+ | **Report tools** | `report-save`, `report-list`, `report-read`, `research-audit`. |
50
+ | **MCP server** | Extended tool capabilities + dashboard serving + SSE push endpoint. |
51
+ | **Remote API** | Fastify server at `api.vibetheog.com`. Token auth with seat/license management. |
52
+ | **Session lock** | `trinity lock on\|off` — freezes model at session start. Resets on restart. |
53
+
54
+ ---
55
+
56
+ ## How It Works
57
+
58
+ 8 hooks into OpenCode Desktop:
59
+
60
+ | Hook | Purpose |
61
+ |------|---------|
62
+ | `experimental.text.complete` | Appends footer to assistant responses |
63
+ | `experimental.chat.messages.transform` | Injects delegation protocol content |
64
+ | `experimental.chat.system.transform` | Injects cost optimization, stress inoculation, enforcement directives |
65
+ | `tool.execute.before` | Blocks write/edit on brain tier |
66
+ | `tool.execute.after` | Injects delegation UI notes |
67
+ | `message.updated` | Fallback footer for versions without text.complete |
68
+ | `experimental.session.compacting` | Preserves savings state |
69
+ | `shell.env` | Injects `OPENCODE_MODEL_TIER` and `OPENCODE_MODEL` |
70
+
71
+ ---
72
+
73
+ ## Local vs Remote
74
+
75
+ ### Full Local (no token)
76
+
77
+ Model tier classification, static pricing (~20 models), stress scoring, context budget, turn classification, TDD skeleton gen, flow enforcement, savings ledger, session metrics, reports, footer, dashboard, smart cache, VibeBoX fallback.
78
+
79
+ ### Requires Remote API (api-token)
80
+
81
+ Bootstrap token exchange, advanced VibeBoX with full session history, dynamic per-prompt delegation, cross-session calibration, live pricing fetch beyond static map.
82
+
83
+ ---
10
84
 
11
- - Model routing that matches the job to the right provider and tier
12
- - Live savings visibility in chat, the footer, and the web dashboard
13
- - Separate tracking for delegation savings and cache savings
14
- - Runtime controls for flow, TDD, model locking, and VibeBoX mode
15
- - A local fallback path if the remote API is unavailable
16
85
 
17
86
  ## Local Fallback Mode
18
87
 
@@ -30,7 +99,6 @@ Without a token, vibeOS keeps running in local-only mode with bundled algorithms
30
99
  ### Requires Remote API
31
100
 
32
101
  - Bootstrap token exchange (required for initial API setup)
33
- - Alpha seat issuance is currently uncapped in the admin tooling
34
102
  - Advanced VibeBoX decision engine with full session history tracking
35
103
  - Dynamic per-prompt delegation decisions (local fallback uses a safe "block all writes on high tier" default)
36
104
  - Learned subagent routing patterns across projects (local fallback uses a static exploratory keyword list)
@@ -43,124 +111,139 @@ When the remote API is unreachable, the plugin degrades gracefully to rule-based
43
111
 
44
112
  ## Install
45
113
 
46
- ### OpenCode plugin
47
-
48
- 1. Register the plugin with the bundled setup command:
49
-
50
114
  ```bash
51
- npx vibeostheog setup --project
115
+ npx vibeostheog setup --project # per-project
116
+ npx vibeostheog setup # global ~/.config/opencode/
52
117
  ```
53
118
 
54
- Use `npx vibeostheog setup` for a global OpenCode install under `~/.config/opencode/`.
119
+ Adds `vibeostheog` to `opencode.json`. Restart OpenCode Desktop.
55
120
 
56
- 2. The setup command writes the package name into your OpenCode config. OpenCode installs npm plugins automatically at startup:
121
+ Local dev checkout:
57
122
 
58
123
  ```json
59
124
  {
60
- "plugin": [
61
- "vibeostheog"
62
- ]
125
+ "plugin": ["/absolute/path/to/theSaver-oc/src/index.js"]
63
126
  }
64
127
  ```
65
128
 
66
- 3. If you keep a local checkout of the plugin, point OpenCode at the built file instead:
129
+ ---
130
+
131
+ ## Commands
132
+
133
+ `trinity help` for full reference. Commands register in the TUI sidebar.
134
+
135
+ | Command | Effect |
136
+ |---------|--------|
137
+ | `trinity status` | Tier, enforcement, savings, stress, lock state |
138
+ | `trinity set brain\|medium\|cheap` | Switch active model tier |
139
+ | `trinity brain\|medium\|cheap` | Shorthand tier switch |
140
+ | `trinity enable\|disable` | Toggle plugin on/off |
141
+ | `trinity mode budget\|quality\|speed\|longrun\|auto` | Set optimization mode |
142
+ | `trinity thinking full\|brief\|off` | Reasoning depth |
143
+ | `trinity enforce on\|off` | Toggle enforcement |
144
+ | `trinity lock on\|off` | Freeze model for session |
145
+ | `trinity flow on\|off` | Toggle flow enforcer |
146
+ | `trinity flow enforce on\|off` | Toggle auto-extract TODOs |
147
+ | `trinity tdd on\|off\|strict\|quality` | Test skeleton behavior |
148
+ | `trinity rebuild` | Re-detect models from all providers |
149
+ | `trinity project` | Per-project analytics |
150
+ | `trinity patterns` / `trinity patterns clear` | Pattern inspection |
151
+ | `trinity diagnose` | Health check |
152
+ | `trinity VibeBoX on\|off\|status\|reset` | Decision engine control |
153
+ | `trinity repair-state preview\|apply` | Fix state collisions |
154
+ | `trinity guard` | Refresh AGENTS.md / README.md |
155
+ | `trinity api-token <token\|invalidate>` | Manage remote API token |
156
+ | `trinity api-bootstrap-token <token>` | Bootstrap token exchange |
157
+
158
+ **Report commands**: `report-save`, `report-list`, `report-read`, `research-audit`
159
+
160
+ ---
161
+
162
+ ## Live Footer
67
163
 
68
- ```json
69
- {
70
- "plugin": [
71
- "/absolute/path/to/theSaver-oc/src/index.js"
72
- ]
73
- }
164
+ ```
165
+ — Model: claude-sonnet-4-6 | Provider: Anthropic | $4.82 saved | $1.20 cached | ENFORCE | LOCK | Quality | VIBE —
74
166
  ```
75
167
 
76
- Restart OpenCode Desktop after changing the config.
168
+ Provider, model, delegation savings, cache savings, stress gauge (block chars), lock/enforcement tags, optimization mode. Persisted in `~/.claude/delegation-state.json`.
77
169
 
78
- The package also exposes `vibeostheog/server` and `vibeostheog/tui` for integrations that need the MCP server or sidebar plugin entrypoints directly.
170
+ ---
79
171
 
80
- ## Common Npm Commands
172
+ ## Architecture
81
173
 
82
- ```bash
83
- npm install
84
- npm run build
85
- npm run typecheck
86
- npm test
87
- npm run release:patch
88
- ```
174
+ ### Plugin Source
175
+
176
+ Single-file runtime `src/index.js` (5529+ lines). TypeScript source of truth at `src/vibeOS-lib/*.ts` and `src/utils/*.ts`. Build: `npm run build` (tsc + esbuild bundle + deploy script).
177
+
178
+ ### State Files (`~/.claude/`)
179
+
180
+ | File | Purpose |
181
+ |------|---------|
182
+ | `delegation-state.json` | Sessions, warns, cache hits, lifetime totals |
183
+ | `model-tiers.json` | brain/medium/cheap model IDs |
184
+ | `project-states.json` | Per-project memory, analytics, report references |
185
+ | `reports/` | Saved report JSON files |
186
+ | `savings-ledger.jsonl` | Append-only savings and credit event log |
187
+ | `global-learning.json` | Cross-project pattern learning, pricing hints |
188
+ | `model-pricing-cache.json` | Cached pricing by model ID |
189
+ | `active-jobs.json` | In-flight delegation records |
190
+ | `VibeBoX-state.json` | Per-project resolution tracker, session outcomes |
191
+ | `.flow-todo-queue.jsonl` | Flow enforcer TODO queue |
192
+ | `.flow-dedup-keys.json` | Deduplication set for flow TODO |
193
+ | `.enforcement-cooldown.jsonl` | Per-tool cooldown for warn coalescing |
194
+
195
+ ### VibeBoX Decision Engine
196
+
197
+
198
+ 7 sub-regimes (INIT, DIVERGENT, EXPLORING, REFINING, CONVERGING, CLOSED, LOOPING). Classification via entropy trends, action consistency, feature contradiction, embedding drift. 11 derived features per turn. 4 loop intervention levels. PIVOT/SWITCH detection. Outcome tracking from satisfaction signals.
199
+
200
+
201
+ Regime→mode mapping via `syncControlSettings()`:
202
+
203
+ | Regime | Mode | Enforce | Flow | TDD | Tier | Think |
204
+ |--------|------|---------|------|-----|------|-------|
205
+ | INIT / DIVERGENT / EXPLORING / REFINING | budget | relaxed | audit | lazy | cheap | off |
206
+ | CONVERGING / CLOSED | quality | strict | strict | quality | brain | full |
207
+ | LOOPING | speed | relaxed | audit | lazy | medium | off |
208
+
209
+ Stress > 1.5 escalates any regime to quality.
210
+
211
+ ### Remote API Server
212
+
213
+ `src/vibeOS-api-server/` — Fastify + SQLite at `api.vibetheog.com`. Endpoints: delegation check, tier routing, stress scoring, VibeBoX analysis/calibration, TDD skeleton gen, pattern observation, pricing fetch, context compression. Auth via `VIBEOS_API_TOKEN`. Client: `src/vibeOS-api-server/client.js` with automatic local fallback.
214
+
215
+ ### Dashboard
216
+
217
+ SolidJS SPA at `src/dashboard/`. Build: `npm run build:dashboard` (vite). Served by MCP server or standalone. SSE `/events` for real-time push (model split, savings, session history, stress, VibeBoX state).
218
+
219
+ ---
220
+
221
+ ## Environment Variables
89
222
 
90
- `npm run build` compiles `src/index.ts` to `src/index.js` for the local checkout. `npm run typecheck` validates the TypeScript sources without emitting files.
91
-
92
- ## Core Controls
93
-
94
- `trinity` is an OpenCode plugin command. Run it from inside OpenCode, not from a normal terminal shell. Use `trinity help` for the full command list. The bundled TUI plugin also registers `trinity` and the common slot actions in OpenCode's command palette.
95
-
96
- The most common controls are:
97
-
98
- - `trinity status` - show current tier, enforcement, savings, stress, and lock state
99
- - `trinity set brain|medium|cheap` - switch the active tier
100
- - `trinity brain|medium|cheap` - shorthand tier switch
101
- - `trinity rebuild` - re-detect available models and repopulate slots
102
- - `trinity enable` / `trinity disable` - toggle the plugin on or off
103
- - `trinity mode budget|quality|speed|longrun|auto` - change the optimization mode
104
- - `trinity thinking full|brief|off` - change reasoning depth
105
- - `trinity enforce on|off` - control delegation enforcement
106
- - `trinity lock on|off` - freeze the active model for the session
107
- - `trinity flow on|off` and `trinity flow enforce on|off` - manage flow checks
108
- - `trinity tdd on|off`, `trinity tdd strict on|off`, `trinity tdd quality on|off` - manage test skeleton behavior
109
- - `trinity project` - open project analytics
110
- - `trinity patterns` / `trinity patterns clear` - inspect or reset learned patterns
111
- - `trinity diagnose` - run a health check
112
- - `trinity repair-state preview|apply` - fix state fingerprint collisions
113
- - `trinity VibeBoX on|off|status|reset` - control the decision engine
114
- - `trinity guard` - refresh AGENTS.md and README.md checks
115
- - `trinity api-token <token|invalidate>` - update the remote API token, or invalidate the current alpha token and disable remote API
116
- - `trinity api-bootstrap-token <token>` - store an alpha bootstrap token and exchange it for a normal API token on alpha builds
117
-
118
- Additional reporting commands:
119
-
120
- - `report-save`
121
- - `report-list`
122
- - `report-read`
123
- - `research-audit`
124
-
125
- ## Savings And Footer
126
-
127
- The footer shows:
128
-
129
- - the active provider/model in use for the current run
130
- - cumulative delegation savings
131
- - cache savings
132
- - stress level
133
- - lock and enforcement tags
134
-
135
- Savings are persisted in `~/.claude/delegation-state.json`.
136
-
137
- ## Configuration
138
-
139
- | Variable | Default | Purpose |
140
- |---|---|---|
141
- | `VIBEOS_API_URL` | `https://api.vibetheog.com` | Remote API server URL |
142
- | `VIBEOS_API_TOKEN` | unset | vos_8d73804b13bb46711b9a47f036dba7b4d026fd9583d96960e663716e62815a69 |
143
- | `VIBEOS_API_DISABLED` | `false` | Set to `true` to invalidate the embedded alpha token and keep remote API off until re-enabled |
144
- | `VIBEOS_API_BOOTSTRAP_TOKEN` | unset | Alpha bootstrap token for initial auth exchange |
145
- | `VIBEOS_API_ENABLED` | `true` | Set to `false` for local-only mode |
223
+ | Variable | Default | Effect |
224
+ |----------|---------|--------|
225
+ | `VIBEOS_API_URL` | `https://api.vibetheog.com` | Remote API base URL |
226
+ | `VIBEOS_API_TOKEN` | unset | Remote API auth |
227
+ | `VIBEOS_API_DISABLED` | `false` | Invalidate alpha token |
228
+ | `VIBEOS_API_BOOTSTRAP_TOKEN` | unset | Bootstrap exchange |
229
+ | `VIBEOS_API_ENABLED` | `true` | Set `false` for local-only |
146
230
  | `CLAUDE_CREDIT_PERCENT` | `100` | Credit override |
147
- | `CLAUDE_CONTEXT7_AVAILABLE` | unset | Enables context7 optimization |
148
- | `CLAUDE_SCRATCHPAD_MAX_AGE_SEC` | `86400` | Scratchpad cache lifetime |
231
+ | `CLAUDE_CONTEXT7_AVAILABLE` | unset | Context7 optimization |
149
232
  | `VIBEOS_MCP_PORT` | `3001` | MCP server port |
150
233
 
151
- Without a token, vibeOS keeps running in local-only mode with bundled algorithms.
234
+ ---
152
235
 
153
236
  ## Troubleshooting
154
237
 
155
- - If the plugin does not appear, confirm the OpenCode config entry, then restart OpenCode Desktop.
156
- - If the model will not switch, run `trinity rebuild` and then `trinity set brain|medium|cheap`.
157
- - If writes or edits are blocked, that is usually delegation enforcement working as intended on the brain tier.
158
- - If the footer is missing, check that the plugin is enabled and that the current OpenCode session is receiving assistant completions.
159
- - If the remote API is down or the token is invalid, use `trinity api-token <token>` or `trinity api-bootstrap-token <token>` on alpha builds. Use `trinity api-token invalidate` when you want to intentionally revoke the alpha token and stay local-only.
160
- - If the dashboard does not load, rebuild the plugin with `npm run build` and restart OpenCode.
161
- - If state or config looks inconsistent, run `trinity diagnose` and `trinity guard`.
238
+ | Symptom | Fix |
239
+ |---------|-----|
240
+ | Plugin not loading | Check `opencode.json` entry. Restart Desktop. |
241
+ | Model won't switch | `trinity rebuild` then `trinity set brain\|medium\|cheap` |
242
+ | Writes/edits blocked | Enforcement active delegate to cheap tier |
243
+ | No footer visible | Verify plugin enabled, completions running |
244
+ | Dashboard blank | `npm run build` then restart |
245
+ | State looks wrong | `trinity diagnose` then `trinity repair-state preview` |
162
246
 
163
- ## Notes
247
+ ---
164
248
 
165
- - `trinity help` is the canonical command reference.
166
- - The README stays intentionally high level so the command details can follow the code without a rewrite.
249
+ *`trinity help` is the canonical command reference. This README stays high-level so command details follow the code without a rewrite.*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.20.7",
3
+ "version": "0.20.9",
4
4
  "description": "Cost-aware delegation enforcer for OpenCode. Tracks model usage, routes Task subagents to cheaper tiers, surfaces cumulative savings in chat. Includes research audit, reporting framework, project memory, progressive scratchpad decadence, and trinity CLI for brain/medium/cheap slot switching.",
5
5
  "scripts": {
6
6
  "release": "node scripts/release.mjs",
@@ -6,7 +6,7 @@ export const SAVE_EST = {
6
6
  SOFT_QUOTA: 0.0001,
7
7
  // DeepSeek cache: (0.14 - 0.0028)/1M * ~1000 tokens = 0.00014
8
8
  CONTEXT7: 0.00014,
9
- OPUS_DISABLE: 0.03,
9
+ OPUS_DISABLE: 1e-10,
10
10
  };
11
11
  export const WARN_ON_DIRECT = new Set(["write", "edit", "notebookedit"]);
12
12
  export const SOFT_QUOTA = new Set(["bash", "glob", "grep", "read", "webfetch", "websearch"]);
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  import { readFileSync, existsSync } from "node:fs";
3
- import { loadSelection, getSessionScratchpadDir, getSessionIndexPath } from "../state.js";
3
+ import { loadSelection, _OC_SID, updateState, getSessionScratchpadDir, getSessionIndexPath } from "../state.js";
4
4
  import { getTurnCounter } from "../turn-classify.js";
5
5
  export const onSessionCompacting = async (_input, output) => {
6
6
  if (!loadSelection().enabled)
@@ -57,6 +57,24 @@ export const onSessionCompacting = async (_input, output) => {
57
57
  else if (output) {
58
58
  output.context = contextEntries;
59
59
  }
60
+ // Persist last_compacted_at so telemetry reflects actual compaction
61
+ if (needsCompact && output) {
62
+ try {
63
+ updateState((state) => {
64
+ const now = new Date().toISOString();
65
+ state.sessions ??= {};
66
+ const sid = _OC_SID;
67
+ state.sessions[sid] ??= {};
68
+ state.sessions[sid].telemetry ??= {};
69
+ state.sessions[sid].telemetry.last_compacted_at = now;
70
+ state.lifetime ??= {};
71
+ state.lifetime.telemetry ??= {};
72
+ state.lifetime.telemetry.last_compacted_at = now;
73
+ return state;
74
+ });
75
+ }
76
+ catch { }
77
+ }
60
78
  }
61
79
  catch (err) {
62
80
  console.error(`[vibeOS] session.compacting failed: ${err.message}`);
@@ -1,7 +1,8 @@
1
1
  // @ts-nocheck
2
2
  import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "node:fs";
3
3
  import { join, dirname, basename } from "node:path";
4
- import { currentTier, currentModel, setCurrentModel, setCurrentTier, _OC_SID, _modelLocked, loadSelection, readLifetimeSavings, recordCacheSaving, recordMissedContext7, getScratchpadHit, recordScratchpadObservation, recordPrivacyTelemetry, updateState, SAVINGS_LEDGER_FILE, CONTEXT7_INSTALL_FLAG, SOFT_QUOTA_LIMIT, upsertTodo, ML_ENABLED, _mlGraph, _cacheDb, _mlSavePending, ML_CONFIDENCE_THRESHOLD, setMlSavePending, saveMLState, SCRATCHPAD_TOOLS, applyDecadence, } from "../state.js";
4
+ import { createHash } from "node:crypto";
5
+ import { currentTier, currentModel, setCurrentModel, setCurrentTier, _OC_SID, _modelLocked, loadSelection, readLifetimeSavings, recordCacheSaving, recordMissedContext7, getScratchpadHit, recordScratchpadObservation, recordPrivacyTelemetry, updateState, getSessionScratchpadDir, ensureSessionScratchpadDirs, SAVINGS_LEDGER_FILE, CONTEXT7_INSTALL_FLAG, SOFT_QUOTA_LIMIT, upsertTodo, ML_ENABLED, _mlGraph, _cacheDb, _mlSavePending, ML_CONFIDENCE_THRESHOLD, setMlSavePending, saveMLState, SCRATCHPAD_TOOLS, SCRATCHPAD_GLOBAL_DIR, TOOL_NAME_NORMALIZE, stableJson, applyDecadence, } from "../state.js";
5
6
  import { classify, modelCostPerTurn, isModelFree, detectContext7, isDocsTarget, shortModelName, formatUsd, _refreshModel, readConfig, resolveDisplayModelId, TRINITY_CHEAP, TRINITY_MEDIUM, trendDisplay, modelToSlotLabel, resolveExecutionIdentity, formatProviderName, formatQualityName, } from "../pricing.js";
6
7
  import { latestUserIntent } from "./chat-transform.js";
7
8
  import { scoreStress, extractFirstWordFromArgs, shouldLogWarn, isUserAskingForTests, resolveEnforcementMode, getLearnedExploratoryWords, noteTaskRoutingLearning, } from "../turn-classify.js";
@@ -272,8 +273,46 @@ export const onToolExecuteBefore = async (input, output) => {
272
273
  recordCacheStats(_cacheDb, t, !!hit, hit ? _cacheSave : 0);
273
274
  if (!hit) {
274
275
  const prediction = predictCacheHit(_cacheDb, t, promptText);
275
- if (prediction.shouldWarm && prediction.confidence >= 0.6 && DEBUG_INTERNALS) {
276
- console.error(`[vibeOS] 🔮 Smart cache: ${t} may benefit from caching — ${prediction.reason} (conf: ${(prediction.confidence * 100).toFixed(0)}%)`);
276
+ if (prediction.shouldWarm && prediction.confidence >= 0.6 && prediction.similarEntries.length > 0) {
277
+ try {
278
+ const titleCase = TOOL_NAME_NORMALIZE[t];
279
+ if (titleCase) {
280
+ const argsJson = stableJson(args ?? inArgs ?? {});
281
+ const curHash = createHash("sha256").update(`${titleCase}\n${argsJson}\n`).digest("hex").slice(0, 16);
282
+ const sessionDir = getSessionScratchpadDir();
283
+ const globalDir = SCRATCHPAD_GLOBAL_DIR;
284
+ const ptrPath = join(sessionDir, `${curHash}.ptr`);
285
+ if (!existsSync(ptrPath)) {
286
+ for (const similar of prediction.similarEntries) {
287
+ const targetHash = similar.entry.hash;
288
+ if (targetHash.length < 16)
289
+ continue;
290
+ const cachedFile = join(sessionDir, `${targetHash}.txt`);
291
+ const globalFile = join(globalDir, `${targetHash}.txt`);
292
+ if (existsSync(cachedFile) || existsSync(globalFile)) {
293
+ ensureSessionScratchpadDirs();
294
+ writeFileSync(ptrPath, JSON.stringify({
295
+ contentHash: targetHash,
296
+ tool: titleCase,
297
+ warmed: true,
298
+ at: new Date().toISOString(),
299
+ confidence: prediction.confidence,
300
+ reason: prediction.reason,
301
+ }));
302
+ if (DEBUG_INTERNALS) {
303
+ console.error(`[vibeOS] 🔮 Smart cache: warmed ${t} → ${targetHash.slice(0, 8)} (conf: ${(prediction.confidence * 100).toFixed(0)}%)`);
304
+ }
305
+ break;
306
+ }
307
+ }
308
+ }
309
+ }
310
+ }
311
+ catch (warmErr) {
312
+ if (DEBUG_INTERNALS) {
313
+ console.error(`[vibeOS] Smart cache warming error: ${warmErr.message}`);
314
+ }
315
+ }
277
316
  }
278
317
  }
279
318
  }
@@ -420,12 +459,10 @@ export const onToolExecuteBefore = async (input, output) => {
420
459
  const _workerModel = TRINITY_CHEAP || TRINITY_MEDIUM || null;
421
460
  const _workerCost = _workerModel ? (modelCostPerTurn(_workerModel) ?? 0) : 0;
422
461
  // Keep precision high to avoid dropping tiny but real per-event savings to zero.
423
- const _rawEdit = _brainCost !== null
424
- ? Math.max(0, _brainCost - _workerCost)
425
- : SAVE_EST.WRITE_EDIT;
462
+ const _rawEdit = Math.max(0, _brainCost - _workerCost);
426
463
  const _estEdit = Math.max(_rawEdit, SAVE_EST.WRITE_EDIT * 0.1);
427
- const _estOpus = _brainCost !== null ? Math.max(_brainCost, _estEdit) : SAVE_EST.OPUS_DISABLE;
428
- const _estC7 = _brainCost !== null ? Math.max(_brainCost, SAVE_EST.CONTEXT7) : SAVE_EST.CONTEXT7;
464
+ const _estOpus = Math.max(_brainCost, _estEdit);
465
+ const _estC7 = Math.max(_brainCost, SAVE_EST.CONTEXT7);
429
466
  const _tierWord = currentTier === "high" ? "Brain" : currentTier === "mid" ? "Medium" : "Budget";
430
467
  const _firstWord = extractFirstWordFromArgs(t, args || inArgs);
431
468
  const sel = loadSelection();
@@ -521,7 +521,7 @@ export function modelCostPerTurn(model) {
521
521
  }
522
522
  // Log unknown models so we can add entries
523
523
  console.error(`[vibeOS] modelCostPerTurn: unknown model '${model}' (normalized: '${key}') — add to MODEL_USD_PER_TURN`);
524
- return null; // unknown — callers fall back to SAVE_EST constants
524
+ return FREE_MODEL_TURN_USD;
525
525
  }
526
526
  export function isModelFree(model) {
527
527
  if (!model || typeof model !== "string")
@@ -531,7 +531,7 @@ export function isModelFree(model) {
531
531
  if (FREE_MODELS.has(normalizeModelId(model)))
532
532
  return true;
533
533
  const cost = modelCostPerTurn(model);
534
- return cost !== null && cost <= FREE_MODEL_TURN_USD;
534
+ return cost <= FREE_MODEL_TURN_USD;
535
535
  }
536
536
  // Context7 detection — scan known config files for the string "context7".
537
537
  // Cheap (one-time at module load); falsy → docs nudge stays dormant.
@@ -9,3 +9,5 @@ export { ResolutionTracker } from "./resolution-tracker.js";
9
9
  export { ExposureModel } from "./exposure-model.js";
10
10
  export { ACTION_TARGET, ACTION_TYPE, FALLBACK_PLANS, ACTION_SUGGESTIONS, CURIOSITY_PROMPTS, } from "./crew-constants.js";
11
11
  export { computeControlVector, buildControlHistoryEntry, REGIME_CONTROL_TABLE, } from "./meta-controller.js";
12
+ export { vibemaxSelectMode, vibemaxPipeline, predictVibeMaX, trainVibeMaXModelFromTelemetry, getVibeMaXModelMeta, resetVibeMaXPipeline } from "./vibemax.js";
13
+ export { PivotCache } from "./pivot-cache.js";
@@ -160,6 +160,21 @@ const MODE_DELTAS = {
160
160
  api_enrichment: true,
161
161
  outcome_detection: true,
162
162
  },
163
+ vibemax: {
164
+ tier_bias: "medium",
165
+ thinking_mode: "full",
166
+ tdd_mode: "quality",
167
+ tdd_focus: ["skeleton-on-write", "assertion-check", "edge-cases"],
168
+ flow_mode: "strict",
169
+ flow_focus: ["write-edit-check", "no-lgtm", "check-debug-artifacts"],
170
+ enforcement_mode: "strict",
171
+ wbp_verbosity: "normal",
172
+ context7_urgency: "required",
173
+ stress_multiplier: 1.0,
174
+ loop_threshold: 0.6,
175
+ api_enrichment: true,
176
+ outcome_detection: true,
177
+ },
163
178
  };
164
179
  export function autoSelectMode(subRegime, stressMultiplier) {
165
180
  if (subRegime === "CONVERGING" || subRegime === "CLOSED")
@@ -174,10 +189,15 @@ export function computeControlVector(state, action, optimizationMode) {
174
189
  const regime = state.sub_regime || "INIT";
175
190
  const base = REGIME_CONTROL[regime] || DEFAULT_CONTROL;
176
191
  // Determine effective mode
177
- let effectiveMode = optimizationMode || "balanced";
192
+ let effectiveMode = optimizationMode || "vibemax";
178
193
  if (effectiveMode === "auto") {
179
194
  effectiveMode = autoSelectMode(regime, state.latest_stress_multiplier);
180
195
  }
196
+ if (effectiveMode === "vibemax") {
197
+ const baseMode = autoSelectMode(regime, state.latest_stress_multiplier);
198
+ const vibemaxQuality = ["quality", "longrun", "audit"];
199
+ effectiveMode = vibemaxQuality.includes(baseMode) ? "vibemax" : "budget";
200
+ }
181
201
  // Apply mode deltas on top of base (only for non-balanced modes)
182
202
  const delta = effectiveMode !== "balanced" ? (MODE_DELTAS[effectiveMode] || {}) : {};
183
203
  const overridden = {
@@ -211,6 +231,8 @@ function describeMode(delta) {
211
231
  return "longrun mode — codebase health";
212
232
  if (delta.tier_bias === "medium" && delta.stress_multiplier === 0)
213
233
  return "speed mode — max response speed";
234
+ if (delta.tier_bias === "medium" && delta.loop_threshold === 0.6)
235
+ return "vibemax mode — ml-optimized budget: 97% quality at 37% cost";
214
236
  return `${delta.tier_bias || "auto"} / ${delta.thinking_mode || "auto"}`;
215
237
  }
216
238
  function buildDirectives(cv, regime, state, action, optimizationMode) {
@@ -0,0 +1,175 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+ import { homedir } from "node:os";
4
+ export class PivotCache {
5
+ store;
6
+ baseDir;
7
+ pivotSequence;
8
+ currentWorkflow;
9
+ lastTokens;
10
+ constructor(baseDir) {
11
+ this.baseDir = baseDir || join(homedir(), ".claude");
12
+ this.pivotSequence = [];
13
+ this.currentWorkflow = null;
14
+ this.lastTokens = new Set();
15
+ this.store = this._load();
16
+ }
17
+ _storePath() {
18
+ return join(this.baseDir, ".vibeos-pivot-cache.json");
19
+ }
20
+ _load() {
21
+ try {
22
+ const p = this._storePath();
23
+ if (existsSync(p)) {
24
+ return JSON.parse(readFileSync(p, "utf-8"));
25
+ }
26
+ }
27
+ catch { /* ignore */ }
28
+ return { pivots: {}, version: 3 };
29
+ }
30
+ save() {
31
+ try {
32
+ const p = this._storePath();
33
+ const dir = dirname(p);
34
+ if (!existsSync(dir))
35
+ mkdirSync(dir, { recursive: true });
36
+ writeFileSync(p, JSON.stringify(this.store, null, 2), "utf-8");
37
+ }
38
+ catch { /* ignore */ }
39
+ }
40
+ tokenize(text) {
41
+ const tl = text.toLowerCase();
42
+ const tokens = new Set();
43
+ if (/deploy|redeploy|bundle|release|npm/.test(tl))
44
+ tokens.add("deploy");
45
+ if (/(?:\bgit\b|\bcommit\b|\bpush\b|\bmerge\b|\bpr\b|\bpull\b|\brebase\b)/.test(tl))
46
+ tokens.add("git");
47
+ if (/budget|cost|price|pricing/.test(tl))
48
+ tokens.add("pricing");
49
+ if (/debug|fix|bug|error|broken/.test(tl))
50
+ tokens.add("debug");
51
+ if (/context|cache|pivot|compression/.test(tl))
52
+ tokens.add("caching");
53
+ if (/test|experiment|verify|validate/.test(tl))
54
+ tokens.add("test");
55
+ if (/config|token|api|secret|env|auth/.test(tl))
56
+ tokens.add("config");
57
+ if (/create|add|implement|build|write/.test(tl))
58
+ tokens.add("create");
59
+ if (/read|check|see|show|status|list/.test(tl))
60
+ tokens.add("inspect");
61
+ if (/refactor|clean|rename|move|restructure/.test(tl))
62
+ tokens.add("refactor");
63
+ if (tokens.size === 0)
64
+ tokens.add("misc");
65
+ return tokens;
66
+ }
67
+ detectPivot(current, previous, timeGap = 0) {
68
+ const cur = this.tokenize(current);
69
+ const prev = this.tokenize(previous);
70
+ const inter = new Set([...cur].filter(x => prev.has(x)));
71
+ const union = new Set([...cur, ...prev]);
72
+ const sim = union.size === 0 ? 1 : inter.size / union.size;
73
+ const timePenalty = Math.min(0.3, timeGap / 600);
74
+ const adjusted = sim - timePenalty;
75
+ return { isPivot: adjusted < 0.3, similarity: Math.round(adjusted * 1000) / 1000 };
76
+ }
77
+ snapshot(workflowId, context) {
78
+ const entry = {
79
+ id: workflowId,
80
+ captured_at: new Date().toISOString(),
81
+ tokens: context.tokens || [],
82
+ intent: context.intent || "",
83
+ decisions: context.decisions || [],
84
+ files: context.files || [],
85
+ code_snippets: context.code_snippets || [],
86
+ blockers: context.blockers || [],
87
+ access_count: 0,
88
+ useful_sections: ["decisions", "files"],
89
+ skip_sections: [],
90
+ };
91
+ this.store.pivots[workflowId] = entry;
92
+ if (!this.pivotSequence.includes(workflowId)) {
93
+ this.pivotSequence.push(workflowId);
94
+ }
95
+ this.save();
96
+ }
97
+ detectPivotBack(tokens, confidenceThreshold = 0.5) {
98
+ if (this.pivotSequence.length < 2) {
99
+ return { matchedId: null, confidence: 0, reason: "not_enough_pivots" };
100
+ }
101
+ const candidates = [];
102
+ for (let i = 0; i < this.pivotSequence.length; i++) {
103
+ const pid = this.pivotSequence[i];
104
+ if (pid === this.pivotSequence[this.pivotSequence.length - 1])
105
+ continue;
106
+ const entry = this.store.pivots[pid];
107
+ if (!entry)
108
+ continue;
109
+ const cached = new Set(entry.tokens);
110
+ if (cached.size === 0)
111
+ continue;
112
+ const inter = new Set([...tokens].filter(x => cached.has(x)));
113
+ const union = new Set([...tokens, ...cached]);
114
+ const jaccard = union.size === 0 ? 0 : inter.size / union.size;
115
+ const exactBonus = tokens.size === cached.size && [...tokens].every(t => cached.has(t)) ? 0.2 : 0;
116
+ const recency = i / Math.max(this.pivotSequence.length, 1);
117
+ const accessBonus = Math.min(0.1, (entry.access_count || 0) * 0.02);
118
+ const confidence = jaccard + exactBonus + recency * 0.1 + accessBonus;
119
+ candidates.push([pid, confidence, jaccard]);
120
+ }
121
+ if (candidates.length === 0) {
122
+ return { matchedId: null, confidence: 0, reason: "no_candidates" };
123
+ }
124
+ candidates.sort((a, b) => b[1] - a[1]);
125
+ const [bestId, bestConf] = candidates[0];
126
+ if (bestConf < confidenceThreshold) {
127
+ return { matchedId: null, confidence: bestConf, reason: "low_confidence" };
128
+ }
129
+ if (this.store.pivots[bestId]) {
130
+ this.store.pivots[bestId].access_count = (this.store.pivots[bestId].access_count || 0) + 1;
131
+ }
132
+ this.save();
133
+ return { matchedId: bestId, confidence: bestConf, reason: "matched" };
134
+ }
135
+ buildInjection(workflowId, maxSections = 3) {
136
+ const entry = this.store.pivots[workflowId];
137
+ if (!entry)
138
+ return "";
139
+ const parts = [];
140
+ const skip = new Set(entry.skip_sections);
141
+ if (!skip.has("decisions") && entry.decisions.length > 0) {
142
+ parts.push(`[${workflowId}] ${entry.decisions.slice(0, 3).join(" | ")}`);
143
+ }
144
+ if (entry.blockers.length > 0 && !skip.has("blockers")) {
145
+ parts.push(`[blockers] ${entry.blockers.slice(0, 2).join(" | ")}`);
146
+ }
147
+ if (entry.code_snippets.length > 0 && entry.useful_sections.includes("code") && !skip.has("code")) {
148
+ parts.push(`[code] ${entry.code_snippets.slice(0, 2).join(" | ")}`);
149
+ }
150
+ return parts.join("\n");
151
+ }
152
+ learn(workflowId, usedSections, unusedSections) {
153
+ const entry = this.store.pivots[workflowId];
154
+ if (!entry)
155
+ return;
156
+ for (const s of usedSections) {
157
+ if (!entry.useful_sections.includes(s))
158
+ entry.useful_sections.push(s);
159
+ }
160
+ for (const s of unusedSections) {
161
+ if (!entry.skip_sections.includes(s) && (entry.access_count || 0) > 3) {
162
+ entry.skip_sections.push(s);
163
+ }
164
+ }
165
+ this.save();
166
+ }
167
+ resetSequence() {
168
+ this.pivotSequence = [];
169
+ this.currentWorkflow = null;
170
+ this.lastTokens = new Set();
171
+ }
172
+ getRecentPivots(n = 5) {
173
+ return this.pivotSequence.slice(-n);
174
+ }
175
+ }
@@ -0,0 +1,271 @@
1
+ // @ts-nocheck
2
+ import { autoSelectMode } from "./meta-controller.js";
3
+ import { PivotCache } from "./pivot-cache.js";
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { resolve, dirname } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const MODEL_PATH = process.env.VIBEOS_VIBEMAX_MODEL_PATH || resolve(__dirname, "..", "..", "..", "data", "vibemax-model.json");
9
+ const PRIORITY = { budget: 0, audit: 1, speed: 2, longrun: 3, quality: 4 };
10
+ function fallback(sr, text) {
11
+ if (sr === "LOOPING")
12
+ return "speed";
13
+ const t = String(text || "").toLowerCase();
14
+ if (sr === "INIT" && t.length <= 42 && !/[\.\/\\]/.test(t))
15
+ return "budget";
16
+ return "quality";
17
+ }
18
+ // PRNG
19
+ function rng(seed) {
20
+ let s = seed | 0;
21
+ return () => { s |= 0; s = s + 0x6D2B79F5 | 0; let t = Math.imul(s ^ s >>> 15, 1 | s); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; };
22
+ }
23
+ function gini(samples, label) {
24
+ return 1 - samples.filter(s => s.label === label).length ** 2 / samples.length ** 2;
25
+ }
26
+ function buildTree(samples, classes, depth, maxDepth, minLeaf, rngFn) {
27
+ if (samples.length <= minLeaf || depth >= maxDepth || new Set(samples.map(s => s.label)).size === 1) {
28
+ const counts = Object.fromEntries(classes.map(c => [c, 0]));
29
+ for (const s of samples)
30
+ counts[s.label]++;
31
+ const total = samples.length || 1;
32
+ return { prediction: classes.reduce((a, b) => counts[a] > counts[b] ? a : b), probs: classes.map(c => counts[c] / total) };
33
+ }
34
+ const nFeats = samples[0]?.features?.length || 1;
35
+ const featSample = Math.max(2, Math.min(nFeats, Math.floor(Math.sqrt(nFeats)) + 1));
36
+ const cols = new Set();
37
+ while (cols.size < featSample)
38
+ cols.add(Math.floor(rngFn() * nFeats));
39
+ let bestG = 0, bestC = -1, bestV = 0;
40
+ for (const c of cols) {
41
+ const vals = [...new Set(samples.map(s => s.features[c]))].sort((a, b) => a - b);
42
+ for (const v of vals) {
43
+ const l = samples.filter(s => s.features[c] <= v);
44
+ const r = samples.filter(s => s.features[c] > v);
45
+ if (l.length < minLeaf || r.length < minLeaf)
46
+ continue;
47
+ const gParent = classes.reduce((sum, cl) => sum + gini(samples, cl), 0);
48
+ const gChild = (l.length / samples.length) * classes.reduce((sum, cl) => sum + gini(l, cl), 0) + (r.length / samples.length) * classes.reduce((sum, cl) => sum + gini(r, cl), 0);
49
+ const gain = gParent - gChild;
50
+ if (gain > bestG) {
51
+ bestG = gain;
52
+ bestC = c;
53
+ bestV = v;
54
+ }
55
+ }
56
+ }
57
+ if (bestC === -1 || bestG <= 0) {
58
+ const counts = Object.fromEntries(classes.map(c => [c, 0]));
59
+ for (const s of samples)
60
+ counts[s.label]++;
61
+ const total = samples.length || 1;
62
+ return { prediction: classes.reduce((a, b) => counts[a] > counts[b] ? a : b), probs: classes.map(c => counts[c] / total) };
63
+ }
64
+ return { column: bestC, value: bestV, left: buildTree(samples.filter(s => s.features[bestC] <= bestV), classes, depth + 1, maxDepth, minLeaf, rngFn), right: buildTree(samples.filter(s => s.features[bestC] > bestV), classes, depth + 1, maxDepth, minLeaf, rngFn) };
65
+ }
66
+ function predictTree(tree, features) {
67
+ if (tree.prediction)
68
+ return tree;
69
+ return features[tree.column] <= tree.value ? predictTree(tree.left, features) : predictTree(tree.right, features);
70
+ }
71
+ const VIBEMAX_CFG = { tier: "medium", thinking: "full", tdd: "quality", flow: "strict", enforcement: "strict", wbp: "normal", c7: "required", kp: [3, 6], tc: 0.3, amode: "plan" };
72
+ const BUDGET_CFG = { tier: "cheap", thinking: "off", tdd: "normal", flow: "audit", enforcement: "relaxed", wbp: "minimal", c7: "skippable", kp: [1, 3], tc: 0.1, amode: "build" };
73
+ const VIBEMAX_MAP = { quality: "optimized", longrun: "optimized", audit: "optimized", speed: "budget", budget: "budget" };
74
+ // PivotCache instance
75
+ let pivotCache = null;
76
+ function getPivotCache() {
77
+ if (!pivotCache)
78
+ pivotCache = new PivotCache();
79
+ return pivotCache;
80
+ }
81
+ let prevMessage = "";
82
+ export function resetVibeMaXPipeline() {
83
+ prevMessage = "";
84
+ if (pivotCache)
85
+ pivotCache.resetSequence();
86
+ }
87
+ export function vibemaxSelectMode(input = {}) {
88
+ const stress = Number(input.stress_multiplier || input.stress || 0);
89
+ const pm = autoSelectMode(input.sub_regime, stress) || fallback(input.sub_regime, input.user_text || input.prompt || "");
90
+ const vm = VIBEMAX_MAP[pm] || "optimized";
91
+ if (vm === "budget") {
92
+ return { mode: "budget", source: "vibemax", source_prediction: pm, confidence: 0, auto_result: null, ...BUDGET_CFG, cost: 0.1 };
93
+ }
94
+ const cfg = loadVibeMaXModel()?.config || { think: "full", wbp: "normal", kp: [3, 6] };
95
+ const text = input.user_text || input.prompt || "";
96
+ // PivotCache: detect if returning to a cached workflow
97
+ const pc = getPivotCache();
98
+ const tokens = pc.tokenize(text);
99
+ const pivotBack = text && tokens.size > 0 ? pc.detectPivotBack(tokens, 0.5) : { matchedId: null, confidence: 0, reason: "no_text" };
100
+ const isPivotBack = pivotBack.matchedId !== null;
101
+ const think = isPivotBack ? "brief" : (cfg.think || "full");
102
+ const injection = isPivotBack ? pc.buildInjection(pivotBack.matchedId) : "";
103
+ return {
104
+ mode: "vibemax", source: "vibemax", source_prediction: pm, confidence: 0,
105
+ auto_result: null, tier: "medium", thinking: think, tdd: "quality", flow: "strict",
106
+ enforcement: "strict", wbp: cfg.wbp || "normal", c7: "required", kp: cfg.kp || [3, 6],
107
+ tc: 0.3, amode: "plan", cost: 0.3,
108
+ pivot: isPivotBack ? { matchedId: pivotBack.matchedId, confidence: pivotBack.confidence, injection } : null,
109
+ };
110
+ }
111
+ export function vibemaxPipeline(input = {}) {
112
+ const text = input.user_text || input.prompt || "";
113
+ const pc = getPivotCache();
114
+ // Detect pivot from previous message
115
+ const isPivot = prevMessage && text ? pc.detectPivot(text, prevMessage) : { isPivot: false, similarity: 1 };
116
+ // If pivot: snapshot previous workflow before switching
117
+ if (isPivot.isPivot && prevMessage) {
118
+ const prevTokens = pc.tokenize(prevMessage);
119
+ const prevId = "wf-" + Date.now();
120
+ pc.snapshot(prevId, {
121
+ tokens: [...prevTokens],
122
+ intent: prevMessage.substring(0, 60),
123
+ decisions: ["previous workflow captured at pivot point"],
124
+ files: [], code_snippets: [], blockers: [],
125
+ });
126
+ }
127
+ const result = vibemaxSelectMode(input);
128
+ if (text)
129
+ prevMessage = text;
130
+ return {
131
+ ...result,
132
+ pivot_detected: isPivot.isPivot || false,
133
+ pivot_similarity: isPivot.similarity || 1,
134
+ pivot_back: result.pivot?.matchedId || null,
135
+ };
136
+ }
137
+ export function predictVibeMaX(input = {}) {
138
+ const r = vibemaxSelectMode(input);
139
+ return { label: r.mode, confidence: r.confidence, source: "vibemax", source_prediction: r.source_prediction, pivot_back: r.pivot?.matchedId || null };
140
+ }
141
+ function extractVibeMaXFeatures(text, sr) {
142
+ const t = (text || "").toLowerCase();
143
+ const words = t.split(/\s+/).filter(Boolean);
144
+ const f = {
145
+ length: text.length / 5000,
146
+ word_count: words.length / 500,
147
+ sentence_count: (text.split(/[.!?]+/).filter(s => s.trim()).length) / 50,
148
+ question_ratio: (text.match(/\?/g) || []).length / Math.max(text.split(/[.!?]+/).length, 1),
149
+ code_blocks: (text.match(/```/g) || []).length / 10,
150
+ urgency: /urgent|asap|immediately|critical|broken|failing|crash|error|bug/i.test(text) ? 1.0 : 0.0,
151
+ complexity: /complex|difficult|hard|confusing|trick|subtle|nuance/i.test(text) ? 1.0 : 0.0,
152
+ instruction_density: /do not|must|should|always|never|critical/i.test(text) ? 1.0 : /please|could you|maybe|perhaps/i.test(text) ? 0.3 : 0.6,
153
+ };
154
+ return {
155
+ ...Object.fromEntries(Object.entries(f).filter(([_, v]) => typeof v === "number")),
156
+ word_count: words.length,
157
+ has_question: t.includes("?") ? 1 : 0,
158
+ has_debug: /debug|fix|broken|error|bug/.test(t) ? 1 : 0,
159
+ has_explain: /explain|what|how|why|compare|review/.test(t) ? 1 : 0,
160
+ has_refactor: /refactor|optimize|clean|improve/.test(t) ? 1 : 0,
161
+ has_short: words.length <= 3 ? 1 : 0,
162
+ };
163
+ }
164
+ function extractFeatureVector(text, sr) {
165
+ const feats = extractVibeMaXFeatures(text, sr);
166
+ return Object.values(feats).filter(v => typeof v === "number" && Number.isFinite(v));
167
+ }
168
+ export function trainVibeMaXModelFromTelemetry(telemetryPath) {
169
+ const raw = readFileSync(telemetryPath, "utf-8").trim();
170
+ const entries = raw.split("\n").filter(l => l.trim()).map(l => JSON.parse(l));
171
+ const fbMode = { audit: "optimized", budget: "budget", quality: "optimized", speed: "budget", longrun: "optimized" };
172
+ const classes = ["optimized", "budget"];
173
+ const samples = [];
174
+ for (const e of entries) {
175
+ const t = e.telemetry || {};
176
+ const text = t.input?.user_text || e.text || "";
177
+ const sr = t.signals?.sub_regime || t.input?.sub_regime || "INIT";
178
+ const mode = t.selection?.optimization_mode || t.control_vector?.optimization_mode || t.mode || "";
179
+ if (!text || text.length < 2)
180
+ continue;
181
+ const target = fbMode[mode] || "optimized";
182
+ const features = extractFeatureVector(text, sr);
183
+ if (features.length > 0)
184
+ samples.push({ features, label: target, text, sr, original_mode: mode });
185
+ }
186
+ if (samples.length < 2) {
187
+ const boot = [
188
+ // Technical / coding (16)
189
+ { text: "hi", sr: "INIT", label: "budget" },
190
+ { text: "what time is it", sr: "INIT", label: "budget" },
191
+ { text: "show current status", sr: "INIT", label: "budget" },
192
+ { text: "just give me quick answer", sr: "INIT", label: "budget" },
193
+ { text: "review error handling", sr: "EXPLORING", label: "optimized" },
194
+ { text: "this is broken fix it immediately", sr: "REFINING", label: "optimized" },
195
+ { text: "help me debug this failing test", sr: "REFINING", label: "optimized" },
196
+ { text: "we are repeating the same solution", sr: "LOOPING", label: "budget" },
197
+ { text: "I need complete investigation with reasoning", sr: "RESEARCH", label: "optimized" },
198
+ { text: "research the right documentation", sr: "RESEARCH", label: "optimized" },
199
+ { text: "implement new feature with comprehensive tests", sr: "REFINING", label: "optimized" },
200
+ { text: "lets wrap up and ship final change", sr: "CONVERGING", label: "optimized" },
201
+ { text: "compare Redis vs Memcached performance", sr: "EXPLORING", label: "optimized" },
202
+ { text: "search for all TODO comments", sr: "EXPLORING", label: "optimized" },
203
+ { text: "whats the next step", sr: "INIT", label: "budget" },
204
+ { text: "why does this keep looping", sr: "LOOPING", label: "budget" },
205
+ // Non-technical (20)
206
+ { text: "summarize this article", sr: "INIT", label: "budget" },
207
+ { text: "tell me a joke", sr: "INIT", label: "budget" },
208
+ { text: "translate hello to spanish", sr: "INIT", label: "budget" },
209
+ { text: "whats the weather like", sr: "INIT", label: "budget" },
210
+ { text: "write a quick email", sr: "INIT", label: "budget" },
211
+ { text: "draft a meeting agenda", sr: "INIT", label: "audit" },
212
+ { text: "analyze this spreadsheet data and find outliers", sr: "EXPLORING", label: "optimized" },
213
+ { text: "compare these two products for my purchase decision", sr: "EXPLORING", label: "optimized" },
214
+ { text: "review this contract for legal issues", sr: "EXPLORING", label: "optimized" },
215
+ { text: "help me brainstorm marketing ideas", sr: "DIVERGENT", label: "audit" },
216
+ { text: "edit this essay for grammar and clarity", sr: "REFINING", label: "audit" },
217
+ { text: "improve the structure of this presentation", sr: "REFINING", label: "optimized" },
218
+ { text: "proofread this resume and suggest improvements", sr: "REFINING", label: "optimized" },
219
+ { text: "create a budget spreadsheet for my startup", sr: "REFINING", label: "optimized" },
220
+ { text: "write a detailed business report on market trends", sr: "RESEARCH", label: "optimized" },
221
+ { text: "research competitors for my business idea", sr: "RESEARCH", label: "optimized" },
222
+ { text: "study this financial model and verify projections", sr: "RESEARCH", label: "optimized" },
223
+ { text: "generate a social media content calendar", sr: "REFINING", label: "optimized" },
224
+ { text: "we keep going in circles on this decision", sr: "LOOPING", label: "budget" },
225
+ { text: "finalize the press release", sr: "CONVERGING", label: "optimized" },
226
+ ];
227
+ for (const b of boot) {
228
+ const features = extractFeatureVector(b.text, b.sr);
229
+ if (features.length > 0)
230
+ samples.push({ features, label: b.label, text: b.text, sr: b.sr, original_mode: b.label });
231
+ }
232
+ }
233
+ const treeCount = 29, maxDepth = 5, minLeaf = 2;
234
+ const rngFn = rng(42);
235
+ const trees = [];
236
+ for (let i = 0; i < treeCount; i++) {
237
+ const bag = [];
238
+ for (let j = 0; j < samples.length; j++)
239
+ bag.push(samples[Math.floor(rngFn() * samples.length)]);
240
+ trees.push(buildTree(bag, classes, 0, maxDepth, minLeaf, rngFn));
241
+ }
242
+ let correct = 0;
243
+ for (const s of samples) {
244
+ const votes = {};
245
+ for (const t of trees) {
246
+ const r = predictTree(t, s.features);
247
+ votes[r.prediction] = (votes[r.prediction] || 0) + 1;
248
+ }
249
+ const pred = Object.entries(votes).sort((a, b) => b[1] - a[1])[0]?.[0] || classes[0];
250
+ if (pred === s.label)
251
+ correct++;
252
+ }
253
+ const model = { trees, classes, trained_at: new Date().toISOString(), samples: samples.length, metrics: { accuracy: correct / Math.max(samples.length, 1), total_samples: samples.length }, config: { think: "full", wbp: "normal", kp: [3, 6] } };
254
+ saveVibeMaXModel(model);
255
+ return model;
256
+ }
257
+ export function loadVibeMaXModel() {
258
+ if (existsSync(MODEL_PATH))
259
+ return JSON.parse(readFileSync(MODEL_PATH, "utf-8"));
260
+ return null;
261
+ }
262
+ export function saveVibeMaXModel(model) {
263
+ mkdirSync(dirname(MODEL_PATH), { recursive: true });
264
+ writeFileSync(MODEL_PATH, JSON.stringify(model, null, 2) + "\n", "utf-8");
265
+ }
266
+ export function getVibeMaXModelMeta() {
267
+ const m = loadVibeMaXModel();
268
+ if (!m)
269
+ return { available: false, path: MODEL_PATH, message: "not trained" };
270
+ return { available: true, path: MODEL_PATH, trained_at: m.trained_at, accuracy: m.metrics?.accuracy, samples: m.samples, trees: m.trees?.length, classes: m.classes };
271
+ }