vibeostheog 0.16.0 → 0.18.5

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 (3) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/package.json +1 -1
  3. package/src/index.js +166 -59
package/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## 0.18.5
2
+ - fix: trinity slots now authoritative over opencode.json model
3
+
4
+
5
+ ## 0.18.4
6
+ - fix: quality tracking now computes avg from lifetime score/count instead of hardcoding 0
7
+ - fix: savings rate shown with 4 decimal precision (was rounding to $0.00/hr)
8
+ - fix: cache savings minimum enforced at $0.0001 per scratchpad hit (was rounding to $0)
9
+ - fix: ledger reconciliation flushes buffer before reading + uses Math.max() to prevent state drops
10
+ - fix: model lock no longer overridden by bogus opencode.json model
11
+
12
+ ## 0.18.3
13
+ - feat: dynamic mode injection + footer hooks fix
14
+ - fix: auto-enable plugin on load + always show footer
15
+
16
+
17
+ ## 0.17.0
18
+ - feat: universal context7 detection — scans local opencode.json, ~/.config/opencode/*.json, system PATH, and npm npx cache
19
+ - feat: `_scanOpenCodeConfigs` — finds context7 in any JSON config under ~/.config/opencode/
20
+ - feat: `_context7InPath` — detects context7 binary in system PATH
21
+ - feat: `_context7InNpmCache` — detects context7 in npx cached installations
22
+ - fix: local project opencode.json added to CONTEXT7_CONFIG_FILES search list
23
+
1
24
  ## 0.16.0
2
25
  - feat: dopamine-style footer + natural language system directives
3
26
  - feat: dopamine-style footer + natural language system directives
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.16.0",
3
+ "version": "0.18.5",
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",
package/src/index.js CHANGED
@@ -383,7 +383,7 @@ var init_flow_enforcer = __esm({
383
383
 
384
384
  // src/index.ts
385
385
  init_flow_enforcer();
386
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync14, mkdirSync as mkdirSync10, copyFileSync as copyFileSync5, renameSync as renameSync6 } from "node:fs";
386
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync14, mkdirSync as mkdirSync11, copyFileSync as copyFileSync5, renameSync as renameSync6 } from "node:fs";
387
387
  import { join as join15, dirname as dirname8, basename as basename8 } from "node:path";
388
388
 
389
389
  // src/vibeOS-lib/session-metrics.js
@@ -2516,6 +2516,7 @@ function reconcileStateFromLedger() {
2516
2516
  if (ledgerMtime === _ledgerReconciledMtime)
2517
2517
  return;
2518
2518
  _ledgerReconciledMtime = ledgerMtime;
2519
+ _flushLedgerBuffer();
2519
2520
  const l = readLedgerTotals();
2520
2521
  if (l.total <= 0)
2521
2522
  return;
@@ -2527,8 +2528,8 @@ function reconcileStateFromLedger() {
2527
2528
  return;
2528
2529
  updateState((s) => {
2529
2530
  s.lifetime ??= { warn_count: 0, total_savings_usd: 0, last_updated: "" };
2530
- s.lifetime.total_savings_usd = l.delegation;
2531
- s.lifetime.cache_savings_usd = l.cache;
2531
+ s.lifetime.total_savings_usd = Math.max(l.delegation, stDelegation);
2532
+ s.lifetime.cache_savings_usd = Math.max(l.cache, stCache);
2532
2533
  s.lifetime.last_updated = (/* @__PURE__ */ new Date()).toISOString();
2533
2534
  s.lifetime.rebuilt_from_ledger = true;
2534
2535
  s.lifetime.ledger_entries_reconciled = l.entries;
@@ -2893,6 +2894,41 @@ function _scanOpenCodeConfigs(baseDir) {
2893
2894
  }
2894
2895
  return false;
2895
2896
  }
2897
+ function _context7InPath() {
2898
+ try {
2899
+ const pathDirs = (process.env.PATH || "").split(":");
2900
+ for (const dir of pathDirs) {
2901
+ if (!dir)
2902
+ continue;
2903
+ try {
2904
+ if (existsSync4(join4(dir, "context7")))
2905
+ return true;
2906
+ if (existsSync4(join4(dir, "context7.cmd")))
2907
+ return true;
2908
+ } catch {
2909
+ }
2910
+ }
2911
+ } catch {
2912
+ }
2913
+ return false;
2914
+ }
2915
+ function _context7InNpmCache() {
2916
+ try {
2917
+ const npxDir = join4(USER_HOME3, ".npm/_npx");
2918
+ if (!existsSync4(npxDir))
2919
+ return false;
2920
+ for (const hashDir of readdirSync2(npxDir)) {
2921
+ const ctxDir = join4(npxDir, hashDir, "node_modules", "context7");
2922
+ try {
2923
+ if (existsSync4(join4(ctxDir, "package.json")))
2924
+ return true;
2925
+ } catch {
2926
+ }
2927
+ }
2928
+ } catch {
2929
+ }
2930
+ return false;
2931
+ }
2896
2932
  function detectContext7(files = CONTEXT7_CONFIG_FILES) {
2897
2933
  if (process.env.CLAUDE_CONTEXT7_AVAILABLE)
2898
2934
  return true;
@@ -2905,6 +2941,10 @@ function detectContext7(files = CONTEXT7_CONFIG_FILES) {
2905
2941
  }
2906
2942
  if (_scanOpenCodeConfigs(join4(USER_HOME3, ".config/opencode")))
2907
2943
  return true;
2944
+ if (_context7InPath())
2945
+ return true;
2946
+ if (_context7InNpmCache())
2947
+ return true;
2908
2948
  return false;
2909
2949
  }
2910
2950
  var DOCS_TARGET_RE = /(docs\.|readthedocs|developer\.mozilla|\/api\/|\/reference\/|\/guide\/|npmjs\.com\/package\/|pypi\.org\/project\/|crates\.io\/crates\/|pkg\.go\.dev|api-docs|\/javadoc\/)/i;
@@ -2997,7 +3037,7 @@ function _refreshModel(directory3) {
2997
3037
  console.error(`[vibeOS] auto-detected model: ${currentModel} (tier=${currentTier})`);
2998
3038
  }
2999
3039
  }
3000
- if (!_modelLocked2) {
3040
+ if (!_modelLocked2 && !slotOcModel) {
3001
3041
  const cfgModel = readConfig(directory3) || readConfig(join4(USER_HOME3, ".config/opencode")) || "";
3002
3042
  if (cfgModel && cfgModel !== currentModel) {
3003
3043
  const oldModel = currentModel;
@@ -5127,12 +5167,12 @@ async function probeModel(modelId, auth) {
5127
5167
  }
5128
5168
 
5129
5169
  // src/lib/hooks/footer.js
5130
- import { readFileSync as readFileSync12 } from "node:fs";
5170
+ import { readFileSync as readFileSync12, appendFileSync as appendFileSync6, mkdirSync as mkdirSync8 } from "node:fs";
5131
5171
  import { join as join13 } from "node:path";
5132
5172
  import { homedir as homedir9, tmpdir as tmpdir6 } from "node:os";
5133
5173
 
5134
5174
  // src/lib/hooks/chat-transform.js
5135
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "node:fs";
5175
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, appendFileSync as appendFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "node:fs";
5136
5176
  import { join as join12, basename as basename6 } from "node:path";
5137
5177
  import { homedir as homedir8 } from "node:os";
5138
5178
  import { createHash as createHash3 } from "node:crypto";
@@ -5545,7 +5585,7 @@ async function apiComputeControlVector(state, action, optimizationMode) {
5545
5585
  }
5546
5586
  const opt = (optimizationMode || "balanced").toLowerCase();
5547
5587
  const isRelaxed = opt === "budget" || opt === "speed" || opt === "audit";
5548
- const isStrict = opt === "quality";
5588
+ const isStrict = opt === "quality" || opt === "forensic" || opt === "defense_in_depth" || opt === "reporting";
5549
5589
  return {
5550
5590
  enforcement_mode: isStrict ? "strict" : "normal",
5551
5591
  enforcement_reason: `[optimize: ${opt}] offline fallback`,
@@ -5603,7 +5643,7 @@ function buildProjectBriefing(directory3) {
5603
5643
  const label = currentProjectName || (directory3 ? basename6(directory3) : "");
5604
5644
  if (!label)
5605
5645
  return null;
5606
- return `Working on ${label}. Keep focused on this repository and its conventions.`;
5646
+ return `[project memory] Active project: ${label}. Stay focused on the current repository and prefer the existing workflow.`;
5607
5647
  }
5608
5648
  function ensureProjectSkill(dir, fp2) {
5609
5649
  const skillsDir = join12(dir, ".opencode", "skills");
@@ -5904,20 +5944,20 @@ var onMessagesTransform = async (_input, output) => {
5904
5944
  }
5905
5945
  };
5906
5946
  var C7_URGENCY = {
5907
- required: " This turn, context7 usage is required.",
5908
- optional: " This turn, context7 is optional \u2014 use it if helpful."
5947
+ required: " CRITICAL: context7 usage is REQUIRED this turn.",
5948
+ optional: " (context7 is optional this turn -- use if helpful but not required.)"
5909
5949
  };
5910
5950
  function context7Directive(cv) {
5911
5951
  const urgency = cv?.context7_urgency || "preferred";
5912
- return "When looking up library or framework documentation (docs.*, readthedocs.*, npmjs.com/package/*, pypi.org/project/*, pkg.go.dev, /api/reference/), use mcp__context7__resolve-library-id and mcp__context7__get-library-docs if they are available instead of WebFetch or WebSearch \u2014 they cost less. Saves roughly $0.06 per turn on average." + (C7_URGENCY[urgency] || "");
5952
+ return "[cost policy] If mcp__context7__resolve-library-id and mcp__context7__get-library-docs tools are available in this session, ALWAYS use them instead of WebFetch or WebSearch when looking up library or framework documentation (docs.*, readthedocs.*, npmjs.com/package/*, pypi.org/project/*, pkg.go.dev, /api/reference/). Do not fetch those URLs directly when context7 can serve the same content. This saves ~$0.06/turn on average." + (C7_URGENCY[urgency] || "");
5913
5953
  }
5914
5954
  function thinkingDirective(level) {
5915
5955
  const credit = loadCredit();
5916
5956
  const creditNote = `credit ${credit}%`;
5917
5957
  if (level === "brief") {
5918
- return `You're in brief reasoning mode (${creditNote}). Use extended thinking only for genuinely complex multi-step problems. Keep reasoning concise and skip exploratory scratch work.`;
5958
+ return `[thinking policy] Reasoning depth: BRIEF (manually set, ${creditNote}). Use extended thinking only for genuinely complex multi-step problems. Keep reasoning concise -- skip exploratory scratch work and restatement.`;
5919
5959
  }
5920
- return `Extended thinking is off (${creditNote}). Respond directly and concisely \u2014 thinking tokens cost money, save them for when the user explicitly asks.`;
5960
+ return `[thinking policy] Reasoning depth: OFF (manually set, ${creditNote}). Skip extended thinking entirely. Respond directly and concisely. Every thinking token costs money -- save it for when the user explicitly asks.`;
5921
5961
  }
5922
5962
  function orchestratorDirective(cv, sel) {
5923
5963
  const tierBias = cv?.tier_bias || "auto";
@@ -5929,7 +5969,7 @@ function orchestratorDirective(cv, sel) {
5929
5969
  const cheapModel = TRINITY_CHEAP || "the cheaper model";
5930
5970
  const mediumModel = TRINITY_MEDIUM || "the medium model";
5931
5971
  const targetModel = tierBias === "cheap" ? cheapModel : tierBias === "medium" ? mediumModel : tierBias === "brain" ? brainModel : `${cheapModel} or ${mediumModel}`;
5932
- return `You coordinate the work. Delegate heavy implementation to Task subagents (runs on ${targetModel}). Your job: verify results, fill gaps, and synthesize. Write/Edit tools are blocked on this tier \u2014 delegate all implementation work. Always show the vibeOS cost footer.` + (tierBias !== "auto" ? ` This turn is biased toward ${tierBias} tier.` : "");
5972
+ return `[AI ORCHESTRATOR AGENT] You are an AI orchestrator agent. Delegate heavy work to Task subagents (runs on ${targetModel}). Your role: verify, fill gaps, synthesize. CRITICAL: Write/Edit tools are BLOCKED on this tier. You MUST delegate ALL implementation work to Task subagents. Always display the vibeOS cost footer.` + (tierBias !== "auto" ? ` [tier routing] This turn is biased toward ${tierBias} tier.` : "");
5933
5973
  }
5934
5974
  var TDD_NOTES = {
5935
5975
  lazy: " Skeletons only when explicitly requested.",
@@ -5939,21 +5979,21 @@ var TDD_NOTES = {
5939
5979
  function tddDirective(cv, sel) {
5940
5980
  const tddMode = cv?.tdd_mode || (sel.tdd_strict ? "strict" : "normal");
5941
5981
  const tddFocus = cv?.tdd_focus || [];
5942
- const focusNote = tddFocus.length > 0 ? ` Focus on: ${tddFocus.join(", ")}.` : "";
5943
- return `Auto-create test skeletons for source files you write or edit.${TDD_NOTES[tddMode] || ""}${focusNote} Make sure corresponding test files exist with proper assertions.`;
5982
+ const focusNote = tddFocus.length > 0 ? ` Focus: ${tddFocus.join(", ")}.` : "";
5983
+ return `[tdd enforcement: ${tddMode}] Auto-create skeleton tests for source files being written/edited.${TDD_NOTES[tddMode] || ""}${focusNote} When creating or modifying source files, ensure corresponding test files exist with proper assertions.`;
5944
5984
  }
5945
5985
  function flowDirective(cv, sel) {
5946
5986
  const flowMode = cv?.flow_mode || (sel.flow_enforce ? "normal" : "audit");
5947
5987
  const flowFocus = cv?.flow_focus || [];
5948
- const enforceNote = sel.flow_enforce ? " TODO and FIXME markers are being tracked." : "";
5949
- const focusNote = flowFocus.length > 0 ? ` Focus on: ${flowFocus.join(", ")}.` : "";
5950
- return `Follow project conventions when writing or editing code \u2014 check existing patterns and naming conventions.${enforceNote}${focusNote}`;
5988
+ const enforceNote = sel.flow_enforce ? " TODO/FIXME extraction is active." : "";
5989
+ const focusNote = flowFocus.length > 0 ? ` Focus rules: ${flowFocus.join(", ")}.` : "";
5990
+ return `[flow enforcement: ${flowMode}] Development flow rules are active: write/edit operations are checked against project conventions.${enforceNote}${focusNote} Follow existing code patterns, naming conventions, and project structure.`;
5951
5991
  }
5952
5992
  function flowTodosDirective() {
5953
5993
  const pendingTodos = loadTodos().filter((t) => t.status === "pending").length;
5954
5994
  if (pendingTodos === 0)
5955
5995
  return null;
5956
- return pendingTodos + " extracted TODO or FIXME items are pending. Consider using `todowrite` to add them to the task list.";
5996
+ return "[vibeOS] " + pendingTodos + " extracted TODO/FIXME items are pending. Consider calling `todowrite` to add them to the native task list.";
5957
5997
  }
5958
5998
  function patternDirective(fp2) {
5959
5999
  const patterns = promotedProjectPatterns(fp2);
@@ -5966,11 +6006,11 @@ function patternDirective(fp2) {
5966
6006
  parts.push("Routines: " + routines.map((r) => r.summary).join("; "));
5967
6007
  }
5968
6008
  if (frictions.length > 0) {
5969
- parts.push("Things to watch: " + frictions.map((f) => f.summary).join("; "));
6009
+ parts.push("Frictions: " + frictions.map((f) => f.summary).join("; "));
5970
6010
  }
5971
6011
  if (parts.length === 0)
5972
6012
  return null;
5973
- return "Learned patterns for this project \u2014 " + parts.join(". ") + ".";
6013
+ return "[project patterns] " + parts.join(". ") + ".";
5974
6014
  }
5975
6015
  function welcomeDirective() {
5976
6016
  const sel = loadSelection();
@@ -5981,13 +6021,14 @@ function welcomeDirective() {
5981
6021
  }
5982
6022
  const active = sel.active_slot || "medium";
5983
6023
  const current = currentModel || "(unknown)";
5984
- return "vibeOS is active. Slot: " + active + " (" + current + "). Use `trinity` to switch slots, rebuild, or check status. Run `trinity help` for all commands.";
6024
+ return "[vibeOS] Active plugin. Slot: " + active + " (" + current + "). Use trinity command to switch slots, rebuild, or check status. Run `trinity help` for all commands.";
5985
6025
  }
5986
6026
  function contextBudgetDirective(_input, output) {
5987
6027
  const ctxBudget = estimateContextBudget(_input, output);
5988
6028
  if (!ctxBudget || ctxBudget.pct <= 70)
5989
6029
  return null;
5990
- return `Context is ${ctxBudget.pct}% full (~${ctxBudget.estimatedTokens} tokens). Consider delegating heavy work to Task subagents, compressing tool outputs, or starting a new session.`;
6030
+ const severity = ctxBudget.pct > 90 ? "CRITICAL" : "WARNING";
6031
+ return `[context budget: ${severity}] Context window is ${ctxBudget.pct}% full (~${ctxBudget.estimatedTokens} tokens). Consider using Task subagents for heavy work, compressing tool outputs, or starting a new session to avoid context overflow.`;
5991
6032
  }
5992
6033
  var onSystemTransform = async (_input, output) => {
5993
6034
  if (!loadSelection().enabled)
@@ -6024,9 +6065,9 @@ var onSystemTransform = async (_input, output) => {
6024
6065
  pushSystem(output, thinkingDirective(sel.thinking_level));
6025
6066
  }
6026
6067
  if (stressScore > 0.7) {
6027
- pushSystem(output, "The user seems quite stressed. Stay calm, structured, and thorough. Use clear markdown with code blocks, lists, and organized sections \u2014 do not mirror their tone. This is important.");
6068
+ pushSystem(output, "[stress mitigation: CRITICAL] The user's message shows very high stress indicators. Stay calm, structured, and thorough. Use proper markdown formatting with code blocks, lists, and organized structure -- do NOT mirror the user's tone or brevity. This is the most important directive in your system prompt for this turn.");
6028
6069
  } else if (stressScore > 0.4) {
6029
- pushSystem(output, "The user seems a bit stressed. Keep responses well-structured with clear markdown and organized sections.");
6070
+ pushSystem(output, "[stress mitigation: elevated] The user's message has elevated stress indicators. Maintain structured, well-formatted responses with markdown and code blocks regardless of the prompt's tone.");
6030
6071
  }
6031
6072
  if (_controlVector?.directives?.length > 0) {
6032
6073
  for (const directive of _controlVector.directives) {
@@ -6034,24 +6075,25 @@ var onSystemTransform = async (_input, output) => {
6034
6075
  }
6035
6076
  } else if (_blackboxEnabled && _latestBlackboxState3?.n_interactions > 0) {
6036
6077
  const res = _latestBlackboxState3;
6037
- pushSystem(output, `Current resolution: ${res.resolution || "unresolved"} (${res.sub_regime || "EXPLORING"}). Momentum: ${(res.momentum || 0) > 0 ? "positive" : (res.momentum || 0) < 0 ? "negative" : "neutral"}. If the conversation is looping or stuck, suggest stepping back. If you're converging or closing, push toward a decision.`);
6078
+ pushSystem(output, `[decision engine] Current resolution: ${res.resolution || "unresolved"} (${res.sub_regime || "EXPLORING"}). Momentum: ${(res.momentum || 0) > 0 ? "positive" : (res.momentum || 0) < 0 ? "negative" : "neutral"}. When offering guidance, consider the current resolution state -- if looping or divergent, suggest stepping back; if converging or closed, support decisive action.`);
6038
6079
  if (res.is_looping && res.loop_intervention_level && res.loop_intervention_level !== "none") {
6039
- pushSystem(output, `${_latestBlackboxLoopMsg2 || "The conversation may be circling \u2014 try a fresh angle."} (level: ${res.loop_intervention_level})`);
6080
+ const severity = res.loop_intervention_level === "escalated" ? "CRITICAL" : res.loop_intervention_level === "assertive" ? "WARNING" : "NOTICE";
6081
+ pushSystem(output, `[loop prevention: ${severity}] ${_latestBlackboxLoopMsg2 || "The conversation may be looping -- try a different approach."} (level: ${res.loop_intervention_level})`);
6040
6082
  }
6041
6083
  if (res.pivot_detected && _latestBlackboxPivotMsg2) {
6042
- pushSystem(output, `Topic seems to have shifted: ${_latestBlackboxPivotMsg2}`);
6084
+ pushSystem(output, `[context switch: PIVOT] ${_latestBlackboxPivotMsg2}`);
6043
6085
  }
6044
6086
  }
6045
6087
  const projectJob = getActiveJobForProject();
6046
6088
  if (latestUserIntent && projectJob && isLikelyOffTopic(latestUserIntent, projectJob)) {
6047
- pushSystem(output, `There's an active job: "${(projectJob.prompt || "").slice(0, 140)}...". The latest request looks unrelated. Before acting, ask if they want to switch focus.`);
6089
+ pushSystem(output, `[job-focus] Active job context exists: "${(projectJob.prompt || "").slice(0, 140)}...". The latest user request appears off-topic relative to this running job. Before taking write/edit/task actions, ask one concise confirmation question to validate switching scope.`);
6048
6090
  console.error("[vibeOS] [job-focus] off-topic request detected vs active job context");
6049
6091
  }
6050
6092
  if (sel.delegation_enforce && _controlVector?.enforcement_mode !== "relaxed" && _controlVector?.agent_mode !== "plan") {
6051
6093
  pushSystem(output, orchestratorDirective(_controlVector, sel));
6052
6094
  }
6053
6095
  if (_controlVector?.enforcement_mode !== "relaxed" && _controlVector?.agent_mode !== "plan") {
6054
- pushSystem(output, "When you have multiple independent tasks, run them all in parallel \u2014 it's faster and cheaper. Only sequence them when one depends on another's output.");
6096
+ pushSystem(output, "[batch execution] When you need to run multiple independent Task subagent calls, invoke them ALL in parallel rather than sequentially. Parallel tasks complete faster and reduce total session cost. Only sequence tasks when one depends on the output of another.");
6055
6097
  }
6056
6098
  if (sel.tdd_enforce && _controlVector?.tdd_mode !== "lazy") {
6057
6099
  pushSystem(output, tddDirective(_controlVector, sel));
@@ -6062,7 +6104,21 @@ var onSystemTransform = async (_input, output) => {
6062
6104
  pushSystem(output, flowTodosDirective());
6063
6105
  }
6064
6106
  }
6065
- pushSystem(output, "AGENTS.md and README.md are protected files \u2014 never edit them without asking. When you add new features, update README.md to document them. AGENTS.md defines the project rules \u2014 follow them.");
6107
+ pushSystem(output, "[project guard: CRITICAL] AGENTS.md and README.md are protected by vibeOS. Do NOT modify either file without explicit user permission. When implementing new features, update README.md to document them. AGENTS.md defines that AI agents must ask before changing code -- respect this rule.");
6108
+ const currentMode = loadOptimizationMode();
6109
+ if (currentMode === "quality") {
6110
+ pushSystem(output, "[mode: quality] Prioritize quality and thoroughness. Provide complete edge case coverage, comprehensive error handling, full type annotations, production-grade code with tests. Do not cut corners for brevity.");
6111
+ } else if (currentMode === "forensic") {
6112
+ pushSystem(output, "[mode: forensic] Use forensic analysis depth: evidence-based reasoning tracing each claim to source; multi-hypothesis evaluation considering competing explanations; explicit uncertainty flags for assumptions and trade-offs; structured output with clear reasoning traces; thorough verification of all edge cases and failure modes.");
6113
+ } else if (currentMode === "web-research" || currentMode === "exploration") {
6114
+ pushSystem(output, "[mode: exploration] Use a research-oriented approach: gather information from multiple perspectives before converging; document alternative approaches; flag confidence levels for each finding; synthesize into actionable recommendations.");
6115
+ } else if (currentMode === "defense_in_depth") {
6116
+ pushSystem(output, "[mode: defense in depth] For every component: define the threat model, implement with defenses, then verify the defense handles the threat. Never write code without specifying what it defends against. Consider: injection, broken auth, data exposure, logic errors, race conditions.");
6117
+ } else if (currentMode === "reporting") {
6118
+ pushSystem(output, "[mode: formal report] Structure output as a formal engineering report with: executive summary, methodology, detailed findings with evidence, trade-offs documented, conclusions with confidence levels, and recommendations.");
6119
+ } else if (currentMode === "verify") {
6120
+ pushSystem(output, "[mode: verification-first] Before writing code, declare verification criteria: which edge cases must pass, what invariants must hold. After each code block, include a verification section showing how correctness is established against each criterion.");
6121
+ }
6066
6122
  pushSystem(output, contextBudgetDirective(_input, output));
6067
6123
  if (!oneShot(fp2)) {
6068
6124
  pushSystem(output, buildProjectBriefing(currentProjectName || ""));
@@ -6073,9 +6129,28 @@ var onSystemTransform = async (_input, output) => {
6073
6129
  if (!oneShot("trinity_welcome_" + fp2)) {
6074
6130
  pushSystem(output, welcomeDirective());
6075
6131
  }
6132
+ const calDir = join12(homedir8(), ".claude");
6133
+ const calFile = join12(calDir, "calibration-data.jsonl");
6134
+ const regime = _latestBlackboxState3?.sub_regime || classifyTurnSimple(latestUserIntent || "");
6135
+ const calRecord = JSON.stringify({
6136
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
6137
+ sid: _OC_SID4,
6138
+ mode: currentMode,
6139
+ regime,
6140
+ stress: stressScore,
6141
+ fp: currentProjectFingerprint || ""
6142
+ }) + "\n";
6143
+ try {
6144
+ mkdirSync7(calDir, { recursive: true });
6145
+ appendFileSync5(calFile, calRecord);
6146
+ } catch {
6147
+ }
6076
6148
  if (!oneShot("vibeos_dashboard_instruct")) {
6077
6149
  pushSystem(output, "[vibeOS dashboard display] When the trinity tool returns output starting with '[vibeOS-dashboard]', you MUST use the question tool to display that data in a clean, human-readable format. Use the question field (not the header) to show the dashboard data. Format it with clear sections separated by blank lines, aligned columns with spaces, and plain text only (no emojis, no markdown). The header should be 'vibeOS Dashboard'. Include only one option in options: {label: 'Dismiss', description: ''}. Strip the '[vibeOS-dashboard]' marker line before displaying.");
6078
6150
  }
6151
+ if (!oneShot("vibeos_dopamine_style_" + fp2)) {
6152
+ pushSystem(output, "[tool style: dopamine] When calling the bash tool, use an emoji-prefixed, progress-focused description \u2014 e.g. 'Shell \u26A1 Compiling assets...' or 'Shell \u{1F9EA} Running tests...'. Combine independent bash commands into a single call with && or ;. Never use raw technical labels as tool descriptions.");
6153
+ }
6079
6154
  } catch (err) {
6080
6155
  console.error(`[vibeOS] system.transform failed: ${err.message}`);
6081
6156
  }
@@ -6085,6 +6160,20 @@ var onSystemTransform = async (_input, output) => {
6085
6160
  var _cachedAutoMode = null;
6086
6161
  var _cachedAutoModeTs = 0;
6087
6162
  var AUTO_CACHE_TTL = 6e4;
6163
+ var DEFAULT_REGIME_MAP = {
6164
+ LOOPING: "forensic",
6165
+ DIVERGENT: "forensic",
6166
+ EXPLORING: "web-research",
6167
+ INIT: "web-research",
6168
+ REFINING: "balanced",
6169
+ CONVERGING: "quality",
6170
+ CLOSED: "quality"
6171
+ };
6172
+ function regimeToMode(regime, stress) {
6173
+ if (stress > 1.5)
6174
+ return "quality";
6175
+ return DEFAULT_REGIME_MAP[regime] || "balanced";
6176
+ }
6088
6177
  async function apiAutoSelectMode(regime, stress) {
6089
6178
  const now = Date.now();
6090
6179
  if (_cachedAutoMode && now - _cachedAutoModeTs < AUTO_CACHE_TTL)
@@ -6099,7 +6188,10 @@ async function apiAutoSelectMode(regime, stress) {
6099
6188
  } catch (e) {
6100
6189
  console.error("[vibeOS] apiAutoSelectMode error:", e.message);
6101
6190
  }
6102
- return _cachedAutoMode || "balanced";
6191
+ const fallback = regimeToMode(regime, stress);
6192
+ if (!_cachedAutoMode || _cachedAutoMode === "balanced")
6193
+ _cachedAutoMode = fallback;
6194
+ return _cachedAutoMode || fallback || "balanced";
6103
6195
  }
6104
6196
  var USER_HOME6 = (() => {
6105
6197
  try {
@@ -6136,11 +6228,19 @@ function readLifetimeSavings2() {
6136
6228
  sesCache: roundUsd2(ses?.cache_savings_usd || 0),
6137
6229
  sesTaskDelegations: ses?.task_delegations_count || 0,
6138
6230
  sesDuration: ses?.duration_seconds || 0,
6139
- sesRatePerHour: ses?.rate_per_hour || 0,
6231
+ sesRatePerHour: (() => {
6232
+ const sesTotal = Number(ses?.total_savings_usd || 0) + Number(ses?.cache_savings_usd || 0);
6233
+ if (!sesTotal)
6234
+ return 0;
6235
+ const dur = Number(ses?.duration_seconds || 0);
6236
+ if (dur <= 0)
6237
+ return 0;
6238
+ return Number((sesTotal / (dur / 3600)).toFixed(4));
6239
+ })(),
6140
6240
  sesTrend: ses?.trend || "",
6141
6241
  sesToolBreakdown: ses?.tool_breakdown || {},
6142
6242
  sesModelTurns: ses?.model_turns || {},
6143
- quality_avg: ses?.quality_avg || 0
6243
+ quality_avg: state?.lifetime?.quality_total_count > 0 ? Math.round((state?.lifetime?.quality_total_score || 0) / state?.lifetime?.quality_total_count) : 0
6144
6244
  };
6145
6245
  } catch {
6146
6246
  return { ltTasks: 0, ltCache: 0, ltCost: 0, count: 0, sesTasks: 0, sesCache: 0, sesTaskDelegations: 0, sesDuration: 0, sesRatePerHour: 0, sesTrend: "", sesToolBreakdown: {}, sesModelTurns: {}, quality_avg: 0 };
@@ -6171,8 +6271,6 @@ function scoreTaskQuality(outputText, promptText) {
6171
6271
  return Math.max(0, Math.min(100, score));
6172
6272
  }
6173
6273
  async function _appendFooter(input, output, directory3) {
6174
- if (!loadSelection3().enabled)
6175
- return;
6176
6274
  _refreshModel(directory3);
6177
6275
  let _footerStress = 0;
6178
6276
  if (latestUserIntent)
@@ -6193,7 +6291,7 @@ async function _appendFooter(input, output, directory3) {
6193
6291
  if (messageID && textCompletePainted.has(messageID))
6194
6292
  return;
6195
6293
  const text = typeof output?.text === "string" ? output.text : typeof output?.result === "string" ? output.result : typeof output?.content === "string" ? output.content : "";
6196
- if (!text || text.length < 50) {
6294
+ if (!text) {
6197
6295
  if (messageID)
6198
6296
  textCompletePainted.add(messageID);
6199
6297
  return;
@@ -6268,7 +6366,7 @@ async function _appendFooter(input, output, directory3) {
6268
6366
  const autoActive = await apiAutoSelectMode(autoRegime, autoStress);
6269
6367
  optTagFooter = `[VIBE\u2192${autoActive.toUpperCase()}${flashIcon}]`;
6270
6368
  saveOptimizationMode(autoActive);
6271
- const slot2 = autoActive === "quality" ? "brain" : autoActive === "speed" ? "medium" : "cheap";
6369
+ const slot2 = autoActive === "quality" || autoActive === "forensic" || autoActive === "defense_in_depth" || autoActive === "reporting" ? "brain" : autoActive === "speed" || autoActive === "web-research" || autoActive === "verify" ? "medium" : "cheap";
6272
6370
  if (!_modelLocked) {
6273
6371
  writeSessionSlot(_OC_SID5, slot2);
6274
6372
  if (slot2 === "brain" && TRINITY_BRAIN) {
@@ -6323,6 +6421,11 @@ ${vibeLine} \u2014`;
6323
6421
  const tracker = getBlackboxTracker();
6324
6422
  tracker.recordOutcome(outcome);
6325
6423
  syncOutcomeToApi(outcome);
6424
+ try {
6425
+ mkdirSync8(join13(USER_HOME6, ".claude"), { recursive: true });
6426
+ appendFileSync6(join13(USER_HOME6, ".claude", "calibration-data.jsonl"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), event: "outcome", sid: _OC_SID5, outcome }) + "\n");
6427
+ } catch {
6428
+ }
6326
6429
  }
6327
6430
  }
6328
6431
  } catch {
@@ -6348,12 +6451,12 @@ ${vibeLine} \u2014`;
6348
6451
  }
6349
6452
 
6350
6453
  // src/lib/hooks/tool-execute.js
6351
- import { writeFileSync as writeFileSync11, appendFileSync as appendFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync9 } from "node:fs";
6454
+ import { writeFileSync as writeFileSync11, appendFileSync as appendFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync10 } from "node:fs";
6352
6455
  import { dirname as dirname7, basename as basename7 } from "node:path";
6353
6456
  init_flow_enforcer();
6354
6457
 
6355
6458
  // src/lib/tdd-enforcer.js
6356
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, appendFileSync as appendFileSync5, existsSync as existsSync11, mkdirSync as mkdirSync8, statSync as statSync6, readdirSync as readdirSync3, rmSync as rmSync4, openSync as openSync3 } from "node:fs";
6459
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, appendFileSync as appendFileSync7, existsSync as existsSync11, mkdirSync as mkdirSync9, statSync as statSync6, readdirSync as readdirSync3, rmSync as rmSync4, openSync as openSync3 } from "node:fs";
6357
6460
  import { join as join14, dirname as dirname6 } from "node:path";
6358
6461
  import { createHash as createHash4 } from "node:crypto";
6359
6462
 
@@ -7453,7 +7556,7 @@ var COOLDOWN_MS = 6e4;
7453
7556
  var _enforcementCooldown = /* @__PURE__ */ new Set();
7454
7557
  function _acquireLock(testPath) {
7455
7558
  try {
7456
- mkdirSync8(ENFORCEMENT_LOCK_DIR, { recursive: true });
7559
+ mkdirSync9(ENFORCEMENT_LOCK_DIR, { recursive: true });
7457
7560
  const hash = createHash4("sha256").update(testPath).digest("hex").slice(0, 16);
7458
7561
  const lockPath = join14(ENFORCEMENT_LOCK_DIR, `${hash}.lock`);
7459
7562
  try {
@@ -7510,10 +7613,10 @@ function _isInCooldown(testPath) {
7510
7613
  }
7511
7614
  function _recordCooldown(testPath) {
7512
7615
  try {
7513
- mkdirSync8(dirname6(ENFORCEMENT_COOLDOWN_FILE2), { recursive: true });
7616
+ mkdirSync9(dirname6(ENFORCEMENT_COOLDOWN_FILE2), { recursive: true });
7514
7617
  const hash = createHash4("sha256").update(testPath).digest("hex").slice(0, 16);
7515
7618
  const entry = JSON.stringify({ h: hash, ts: Date.now() }) + "\n";
7516
- appendFileSync5(ENFORCEMENT_COOLDOWN_FILE2, entry);
7619
+ appendFileSync7(ENFORCEMENT_COOLDOWN_FILE2, entry);
7517
7620
  const lines = readFileSync13(ENFORCEMENT_COOLDOWN_FILE2, "utf-8").trim().split("\n").filter(Boolean);
7518
7621
  if (lines.length > 500) {
7519
7622
  writeFileSync10(ENFORCEMENT_COOLDOWN_FILE2, lines.slice(-200).join("\n") + "\n");
@@ -7602,7 +7705,7 @@ function enforceTestFile(filePath) {
7602
7705
  if (!_acquireLock(skeleton.path))
7603
7706
  return null;
7604
7707
  try {
7605
- mkdirSync8(skeleton.dir, { recursive: true });
7708
+ mkdirSync9(skeleton.dir, { recursive: true });
7606
7709
  writeFileSync10(skeleton.path, skeleton.content);
7607
7710
  _enforcementCooldown.add(skeleton.path);
7608
7711
  _recordCooldown(skeleton.path);
@@ -7705,7 +7808,7 @@ var onToolExecuteBefore = async (input, output) => {
7705
7808
  scratchpadHitsSeen2.add(hit.hash);
7706
7809
  const total = recordScratchpadObservation();
7707
7810
  const _inputTokens = Math.max(1, Math.round(hit.sizeBytes / BYTES_PER_TOKEN));
7708
- _cacheSave = Math.round(_inputTokens * CACHE_SAVED_PER_1M_INPUT_TOKENS / 1e6 * 1e3) / 1e3;
7811
+ _cacheSave = Math.max(1e-4, Math.round(_inputTokens * CACHE_SAVED_PER_1M_INPUT_TOKENS / 1e6 * 1e4) / 1e4);
7709
7812
  const cacheSaved = recordCacheSaving(t, _cacheSave, { hash: hit.hash });
7710
7813
  const sumNote = hit.summaryPath ? ` (summary: ${hit.summaryPath})` : "";
7711
7814
  const cacheNote = cacheSaved ? `, cache+$${(cacheSaved.lifetime || 0).toFixed(3)} lt` : "";
@@ -7909,7 +8012,7 @@ var onToolExecuteBefore = async (input, output) => {
7909
8012
  const missed = recordMissedContext7(_estC7);
7910
8013
  if (!existsSync12(CONTEXT7_INSTALL_FLAG)) {
7911
8014
  try {
7912
- mkdirSync9(dirname7(CONTEXT7_INSTALL_FLAG), { recursive: true });
8015
+ mkdirSync10(dirname7(CONTEXT7_INSTALL_FLAG), { recursive: true });
7913
8016
  writeFileSync11(CONTEXT7_INSTALL_FLAG, "");
7914
8017
  } catch {
7915
8018
  }
@@ -7933,8 +8036,6 @@ var onToolExecuteBefore = async (input, output) => {
7933
8036
  }
7934
8037
  };
7935
8038
  var onToolExecuteAfter = async (input, output) => {
7936
- if (!loadSelection().enabled)
7937
- return;
7938
8039
  _refreshModel(projectDirectory);
7939
8040
  let _footerText = "";
7940
8041
  try {
@@ -8054,7 +8155,7 @@ var onToolExecuteAfter = async (input, output) => {
8054
8155
  if (t === "task") {
8055
8156
  const quality = scoreTaskQuality(output?.result || output?.text || "", input?.args?.prompt || "");
8056
8157
  try {
8057
- appendFileSync6(SAVINGS_LEDGER_FILE, JSON.stringify({
8158
+ appendFileSync8(SAVINGS_LEDGER_FILE, JSON.stringify({
8058
8159
  at: (/* @__PURE__ */ new Date()).toISOString(),
8059
8160
  kind: "quality",
8060
8161
  score: quality,
@@ -8329,6 +8430,8 @@ var setShellDirectory = (dir) => {
8329
8430
  var onShellEnv = async (_input, output) => {
8330
8431
  try {
8331
8432
  _refreshModel(directory2 || process.cwd());
8433
+ if (!output)
8434
+ output = {};
8332
8435
  output.env ??= {};
8333
8436
  output.env.OPENCODE_MODEL_TIER = currentTier || "unknown";
8334
8437
  output.env.OPENCODE_MODEL = currentModel || "unknown";
@@ -8381,7 +8484,7 @@ function backupFile(path, label) {
8381
8484
  try {
8382
8485
  if (!existsSync14(path)) return null;
8383
8486
  const bkDir = join15(USER_HOME2, ".claude", ".backups");
8384
- mkdirSync10(bkDir, { recursive: true });
8487
+ mkdirSync11(bkDir, { recursive: true });
8385
8488
  const bk = join15(bkDir, `${basename8(path)}.${label}.${Date.now()}.bak`);
8386
8489
  copyFileSync5(path, bk);
8387
8490
  return bk;
@@ -8423,7 +8526,7 @@ function persistMcpPort(port) {
8423
8526
  tiers.selection ??= {};
8424
8527
  if (Number(tiers.selection.mcp_port) === Number(port)) return;
8425
8528
  tiers.selection.mcp_port = port;
8426
- mkdirSync10(dirname8(TIERS_FILE2), { recursive: true });
8529
+ mkdirSync11(dirname8(TIERS_FILE2), { recursive: true });
8427
8530
  const tmp = TIERS_FILE2 + ".tmp." + Date.now();
8428
8531
  writeFileSync12(tmp, JSON.stringify(tiers, null, 2) + "\n", "utf-8");
8429
8532
  renameSync6(tmp, TIERS_FILE2);
@@ -8693,7 +8796,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
8693
8796
  if (_tiersData2) {
8694
8797
  _tiersData2.selection ??= {};
8695
8798
  if (_tiersData2.selection.mcp_port === void 0) _tiersData2.selection.mcp_port = 9578;
8696
- mkdirSync10(dirname8(TIERS_FILE2), { recursive: true });
8799
+ mkdirSync11(dirname8(TIERS_FILE2), { recursive: true });
8697
8800
  const _tmp = TIERS_FILE2 + ".tmp." + Date.now();
8698
8801
  writeFileSync12(_tmp, JSON.stringify(_tiersData2, null, 2) + "\n", "utf-8");
8699
8802
  renameSync6(_tmp, TIERS_FILE2);
@@ -8751,6 +8854,10 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
8751
8854
  } catch (err) {
8752
8855
  console.error(`[vibeOS] Project Guard init failed: ${err.message}`);
8753
8856
  }
8857
+ try {
8858
+ writeSelection("enabled", true);
8859
+ } catch {
8860
+ }
8754
8861
  const _tiersData = (() => {
8755
8862
  try {
8756
8863
  return safeJsonParse3(readFileSync15(TIERS_FILE2, "utf-8"));
@@ -8848,12 +8955,6 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
8848
8955
  "experimental.chat.messages.transform": async (_input, output) => {
8849
8956
  return onMessagesTransform(_input, output);
8850
8957
  },
8851
- "experimental.text.complete": async (input, output) => {
8852
- await _appendFooter(input, output, directory3);
8853
- },
8854
- "message.updated": async (input, output) => {
8855
- await _appendFooter(input, output, directory3);
8856
- },
8857
8958
  "experimental.session.compacting": async (_input, output) => {
8858
8959
  return onSessionCompacting(_input, output);
8859
8960
  },
@@ -8864,6 +8965,12 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
8864
8965
  if (typeof setShellDirectory === "function") setShellDirectory(directory3 || "");
8865
8966
  return onShellEnv(_input, output);
8866
8967
  },
8968
+ "experimental.text.complete": async (_input, output) => {
8969
+ await _appendFooter(_input, output, directory3);
8970
+ },
8971
+ "message.updated": async (_input, output) => {
8972
+ await _appendFooter(_input, output, directory3);
8973
+ },
8867
8974
  tool: {
8868
8975
  trinity: tool(createTrinityTool(trinityDeps)),
8869
8976
  "research-audit": tool({