vibeostheog 0.15.8 → 0.15.10

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,13 @@
1
+ ## 0.15.10
2
+ - fix: prevent empty footer from message.updated blocking text.complete
3
+ - fix: prevent empty footer from message.updated blocking text.complete
4
+ - fix: deploy copies .env.production alongside plugin
5
+
6
+
7
+ ## 0.15.9
8
+ - fix: VIBEOS_API_TOKEN lookup from __dirname, ~/.claude/, ~/, cwd/
9
+
10
+
1
11
  ## 0.15.8
2
12
  - fix: load VIBEOS_API_TOKEN from .env.production if env var not set
3
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.15.8",
3
+ "version": "0.15.10",
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
@@ -285,12 +285,12 @@ function recordFlowTodo({ filePath, content }) {
285
285
  return 0;
286
286
  }
287
287
  }
288
- var __dirname, RULES_PATH, GUARD_AGENTS_TEMPLATE, GUARD_README_TEMPLATE, STATE_FILE, FLOW_TODO_FILE, FLOW_DEDUP_FILE, MAX_FLOW_TODOS, _flowWarnsSeen, _stateWriter, _cachedRules, _rulesMtime;
288
+ var __dirname2, RULES_PATH, GUARD_AGENTS_TEMPLATE, GUARD_README_TEMPLATE, STATE_FILE, FLOW_TODO_FILE, FLOW_DEDUP_FILE, MAX_FLOW_TODOS, _flowWarnsSeen, _stateWriter, _cachedRules, _rulesMtime;
289
289
  var init_flow_enforcer = __esm({
290
290
  "src/vibeOS-lib/flow-enforcer.js"() {
291
291
  "use strict";
292
- __dirname = dirname(fileURLToPath(import.meta.url));
293
- RULES_PATH = join(__dirname, "flow-rules.json");
292
+ __dirname2 = dirname(fileURLToPath(import.meta.url));
293
+ RULES_PATH = join(__dirname2, "flow-rules.json");
294
294
  GUARD_AGENTS_TEMPLATE = [
295
295
  "# AGENTS.md",
296
296
  "",
@@ -345,7 +345,7 @@ var init_flow_enforcer = __esm({
345
345
  // src/index.ts
346
346
  init_flow_enforcer();
347
347
  import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync15, mkdirSync as mkdirSync10, copyFileSync as copyFileSync6, renameSync as renameSync6 } from "node:fs";
348
- import { join as join17, dirname as dirname8, basename as basename9 } from "node:path";
348
+ import { join as join16, dirname as dirname8, basename as basename9 } from "node:path";
349
349
 
350
350
  // src/vibeOS-lib/session-metrics.js
351
351
  function formatDuration(totalSeconds) {
@@ -3231,7 +3231,7 @@ function applySlot2(slot) {
3231
3231
 
3232
3232
  // src/lib/turn-classify.js
3233
3233
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, appendFileSync as appendFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync5, statSync as statSync6, copyFileSync as copyFileSync4, renameSync as renameSync5, openSync as openSync3, closeSync as closeSync3, rmSync as rmSync3 } from "node:fs";
3234
- import { join as join7, dirname as dirname5, basename as basename5 } from "node:path";
3234
+ import { join as join6, dirname as dirname5, basename as basename5 } from "node:path";
3235
3235
  import { homedir as homedir6, tmpdir as tmpdir4 } from "node:os";
3236
3236
  import { createHash as createHash3 } from "node:crypto";
3237
3237
 
@@ -3492,25 +3492,22 @@ var VibeOSNetworkError = class extends Error {
3492
3492
 
3493
3493
  // src/lib/api-client.js
3494
3494
  import { readFileSync as readFileSync5 } from "node:fs";
3495
- import { join as join6 } from "node:path";
3496
3495
  import { homedir as homedir5 } from "node:os";
3497
3496
  var VIBEOS_API_URL = process.env.VIBEOS_API_URL || "https://api.vibetheog.com";
3498
- var _envToken2 = "";
3499
- try {
3500
- const env = readFileSync5(join6(process.cwd(), ".env.production"), "utf8");
3501
- const m = env.match(/^VIBEOS_API_TOKEN=(.+)$/m);
3502
- if (m) _envToken2 = m[1].trim();
3503
- } catch {
3504
- }
3505
- if (!_envToken2) {
3497
+ var _envToken = "";
3498
+ var _searchDirs = [__dirname, homedir5() + "/.claude", homedir5(), process.cwd()];
3499
+ for (const dir of _searchDirs) {
3506
3500
  try {
3507
- const env = readFileSync5(join6(homedir5(), ".env.production"), "utf8");
3501
+ const env = readFileSync5(dir + "/.env.production", "utf8");
3508
3502
  const m = env.match(/^VIBEOS_API_TOKEN=(.+)$/m);
3509
- if (m) _envToken2 = m[1].trim();
3503
+ if (m) {
3504
+ _envToken = m[1].trim();
3505
+ break;
3506
+ }
3510
3507
  } catch {
3511
3508
  }
3512
3509
  }
3513
- var VIBEOS_API_TOKEN = process.env.VIBEOS_API_TOKEN || _envToken2 || "";
3510
+ var VIBEOS_API_TOKEN = process.env.VIBEOS_API_TOKEN || _envToken || "";
3514
3511
  var VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
3515
3512
  var _apiClient = null;
3516
3513
  var _apiFallbackMode = false;
@@ -3530,15 +3527,13 @@ function isApiFallback() {
3530
3527
  }
3531
3528
  async function remoteCall(method, args, fallbackFn) {
3532
3529
  if (!VIBEOS_API_ENABLED || _apiFallbackMode) {
3533
- if (fallbackFn)
3534
- return fallbackFn();
3530
+ if (fallbackFn) return fallbackFn();
3535
3531
  return null;
3536
3532
  }
3537
3533
  try {
3538
3534
  const client2 = getApiClient();
3539
3535
  if (!client2) {
3540
- if (fallbackFn)
3541
- return fallbackFn();
3536
+ if (fallbackFn) return fallbackFn();
3542
3537
  return null;
3543
3538
  }
3544
3539
  const result = await client2[method](...args);
@@ -3549,13 +3544,13 @@ async function remoteCall(method, args, fallbackFn) {
3549
3544
  if (!_apiFallbackMode) {
3550
3545
  _apiFallbackMode = true;
3551
3546
  _apiFallbackSince = (/* @__PURE__ */ new Date()).toISOString();
3552
- console.error(`[vibeOS] API fallback activated: ${err.message}`);
3547
+ console.error("[vibeOS] API fallback activated: " + err.message);
3553
3548
  }
3554
3549
  if (fallbackFn) {
3555
3550
  try {
3556
3551
  return fallbackFn();
3557
3552
  } catch (fe) {
3558
- console.error(`[vibeOS] fallback also failed: ${fe.message}`);
3553
+ console.error("[vibeOS] fallback also failed: " + fe.message);
3559
3554
  }
3560
3555
  }
3561
3556
  return null;
@@ -3767,11 +3762,11 @@ var USER_HOME4 = (() => {
3767
3762
  return tmpdir4();
3768
3763
  }
3769
3764
  })();
3770
- var FILE_LOCK_DIR3 = join7(USER_HOME4, ".claude/.vibeOS-locks");
3771
- var BLACKBOX_STATE_FILE2 = join7(USER_HOME4, ".claude/blackbox-state.json");
3772
- var GLOBAL_LEARNING_FILE2 = join7(USER_HOME4, ".claude/global-learning.json");
3773
- var STATE_FILE3 = join7(USER_HOME4, ".claude/delegation-state.json");
3774
- var PROJECT_STATE_FILE2 = join7(USER_HOME4, ".claude/project-states.json");
3765
+ var FILE_LOCK_DIR3 = join6(USER_HOME4, ".claude/.vibeOS-locks");
3766
+ var BLACKBOX_STATE_FILE2 = join6(USER_HOME4, ".claude/blackbox-state.json");
3767
+ var GLOBAL_LEARNING_FILE2 = join6(USER_HOME4, ".claude/global-learning.json");
3768
+ var STATE_FILE3 = join6(USER_HOME4, ".claude/delegation-state.json");
3769
+ var PROJECT_STATE_FILE2 = join6(USER_HOME4, ".claude/project-states.json");
3775
3770
  var DFLT_GL2 = { exploratory_words: {}, task_first_words: {}, updatedAt: null };
3776
3771
  var _blackboxTracker = null;
3777
3772
  var _OC_SID2 = "opencode-" + (process.pid || "x") + "-" + Date.now();
@@ -3786,14 +3781,14 @@ var WARN_MAX_PER_SESSION = 3;
3786
3781
  var WARN_COALESCE_THRESHOLD = 10;
3787
3782
  var warnCoalesceCounters = /* @__PURE__ */ new Map();
3788
3783
  function _handleStateCorruption4(path) {
3789
- const backupDir = join7(USER_HOME4, ".claude", ".backups");
3784
+ const backupDir = join6(USER_HOME4, ".claude", ".backups");
3790
3785
  mkdirSync5(backupDir, { recursive: true });
3791
- const backupPath = join7(backupDir, basename5(path) + ".corrupted." + Date.now());
3786
+ const backupPath = join6(backupDir, basename5(path) + ".corrupted." + Date.now());
3792
3787
  try {
3793
3788
  copyFileSync4(path, backupPath);
3794
3789
  } catch {
3795
3790
  }
3796
- const logPath = join7(USER_HOME4, ".claude", ".state-corruption-log.jsonl");
3791
+ const logPath = join6(USER_HOME4, ".claude", ".state-corruption-log.jsonl");
3797
3792
  try {
3798
3793
  appendFileSync5(logPath, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), path, backup: backupPath }) + "\n");
3799
3794
  } catch {
@@ -3801,7 +3796,7 @@ function _handleStateCorruption4(path) {
3801
3796
  }
3802
3797
  function _lockPathFor3(filePath) {
3803
3798
  const hash = createHash3("sha1").update(String(filePath || "")).digest("hex");
3804
- return join7(FILE_LOCK_DIR3, `${hash}.lock`);
3799
+ return join6(FILE_LOCK_DIR3, `${hash}.lock`);
3805
3800
  }
3806
3801
  function withFileLock3(filePath, fn, opts = {}) {
3807
3802
  const staleMs = Number(opts.staleMs || 3e4);
@@ -3864,7 +3859,7 @@ function readJsonOrEmpty2(filePath) {
3864
3859
  }
3865
3860
  function loadTrinityModels() {
3866
3861
  try {
3867
- const p = join7(USER_HOME4, ".claude/model-tiers.json");
3862
+ const p = join6(USER_HOME4, ".claude/model-tiers.json");
3868
3863
  if (!existsSync6(p))
3869
3864
  return { brain: "", cheap: "", medium: "" };
3870
3865
  const j = safeJsonParse3(readFileSync6(p, "utf-8"));
@@ -4108,9 +4103,9 @@ function saveProjectState2(state) {
4108
4103
  function detectTechStack2(dir) {
4109
4104
  const stacks = [];
4110
4105
  try {
4111
- const pkg = safeJsonParse3(readFileSync6(join7(dir, "package.json"), "utf-8"));
4106
+ const pkg = safeJsonParse3(readFileSync6(join6(dir, "package.json"), "utf-8"));
4112
4107
  if (pkg) {
4113
- if (pkg.devDependencies?.typescript || pkg.dependencies?.typescript || existsSync6(join7(dir, "tsconfig.json")))
4108
+ if (pkg.devDependencies?.typescript || pkg.dependencies?.typescript || existsSync6(join6(dir, "tsconfig.json")))
4114
4109
  stacks.push("typescript");
4115
4110
  if (pkg.dependencies?.react || pkg.devDependencies?.react)
4116
4111
  stacks.push("react");
@@ -4119,21 +4114,21 @@ function detectTechStack2(dir) {
4119
4114
  } catch {
4120
4115
  }
4121
4116
  try {
4122
- if (existsSync6(join7(dir, "Cargo.toml")))
4117
+ if (existsSync6(join6(dir, "Cargo.toml")))
4123
4118
  stacks.push("rust");
4124
4119
  } catch {
4125
4120
  }
4126
4121
  try {
4127
- if (existsSync6(join7(dir, "go.mod")))
4122
+ if (existsSync6(join6(dir, "go.mod")))
4128
4123
  stacks.push("go");
4129
4124
  } catch {
4130
4125
  }
4131
4126
  try {
4132
- if (existsSync6(join7(dir, "requirements.txt")))
4127
+ if (existsSync6(join6(dir, "requirements.txt")))
4133
4128
  stacks.push("python");
4134
- if (existsSync6(join7(dir, "setup.py")))
4129
+ if (existsSync6(join6(dir, "setup.py")))
4135
4130
  stacks.push("python");
4136
- if (existsSync6(join7(dir, "pyproject.toml")))
4131
+ if (existsSync6(join6(dir, "pyproject.toml")))
4137
4132
  stacks.push("python");
4138
4133
  } catch {
4139
4134
  }
@@ -4242,7 +4237,7 @@ function saveOptimizationMode(mode) {
4242
4237
 
4243
4238
  // src/lib/research-audit.js
4244
4239
  import { readFileSync as readFileSync7, existsSync as existsSync7 } from "node:fs";
4245
- import { join as join8 } from "node:path";
4240
+ import { join as join7 } from "node:path";
4246
4241
  import { homedir as homedir7, tmpdir as tmpdir5 } from "node:os";
4247
4242
  var USER_HOME5 = (() => {
4248
4243
  try {
@@ -4252,19 +4247,19 @@ var USER_HOME5 = (() => {
4252
4247
  }
4253
4248
  })();
4254
4249
  var _OC_SID3 = "opencode-" + (process.pid || "x") + "-" + Date.now();
4255
- var SCRATCHPAD_ROOT2 = join8(USER_HOME5, ".claude/scratch");
4256
- var SCRATCHPAD_GLOBAL_DIR2 = join8(SCRATCHPAD_ROOT2, "by-hash");
4257
- var SCRATCHPAD_SESSIONS_DIR2 = join8(SCRATCHPAD_ROOT2, "sessions");
4258
- var STATE_FILE4 = join8(USER_HOME5, ".claude/delegation-state.json");
4250
+ var SCRATCHPAD_ROOT2 = join7(USER_HOME5, ".claude/scratch");
4251
+ var SCRATCHPAD_GLOBAL_DIR2 = join7(SCRATCHPAD_ROOT2, "by-hash");
4252
+ var SCRATCHPAD_SESSIONS_DIR2 = join7(SCRATCHPAD_ROOT2, "sessions");
4253
+ var STATE_FILE4 = join7(USER_HOME5, ".claude/delegation-state.json");
4259
4254
  var currentModel2 = null;
4260
4255
  function getSessionRoot2() {
4261
- return join8(SCRATCHPAD_SESSIONS_DIR2, _OC_SID3);
4256
+ return join7(SCRATCHPAD_SESSIONS_DIR2, _OC_SID3);
4262
4257
  }
4263
4258
  function getSessionScratchpadDir2() {
4264
- return join8(getSessionRoot2(), "by-hash");
4259
+ return join7(getSessionRoot2(), "by-hash");
4265
4260
  }
4266
4261
  function getGlobalIndexPath2() {
4267
- return join8(SCRATCHPAD_ROOT2, "index.jsonl");
4262
+ return join7(SCRATCHPAD_ROOT2, "index.jsonl");
4268
4263
  }
4269
4264
  var FETCH_TOOLS = /* @__PURE__ */ new Set(["WebFetch", "WebSearch", "webfetch", "websearch"]);
4270
4265
  function researchAudit({ hours = 24, session: sessionFilter } = {}) {
@@ -4287,8 +4282,8 @@ function researchAudit({ hours = 24, session: sessionFilter } = {}) {
4287
4282
  report.totalFetches++;
4288
4283
  report.totalBytes += e.size || 0;
4289
4284
  const hash = e.hash;
4290
- const summaryPathSession = join8(getSessionScratchpadDir2(), hash + ".summary.txt");
4291
- const summaryPathGlobal = join8(SCRATCHPAD_GLOBAL_DIR2, hash + ".summary.txt");
4285
+ const summaryPathSession = join7(getSessionScratchpadDir2(), hash + ".summary.txt");
4286
+ const summaryPathGlobal = join7(SCRATCHPAD_GLOBAL_DIR2, hash + ".summary.txt");
4292
4287
  const summaryPath = existsSync7(summaryPathSession) ? summaryPathSession : summaryPathGlobal;
4293
4288
  if (existsSync7(summaryPath)) {
4294
4289
  const summary = readFileSync7(summaryPath, "utf-8").slice(0, 200);
@@ -4362,7 +4357,7 @@ function researchAudit({ hours = 24, session: sessionFilter } = {}) {
4362
4357
 
4363
4358
  // src/lib/reporting.js
4364
4359
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync6, statSync as statSync7, copyFileSync as copyFileSync5, rmSync as rmSync4 } from "node:fs";
4365
- import { join as join9, basename as basename6 } from "node:path";
4360
+ import { join as join8, basename as basename6 } from "node:path";
4366
4361
  import { homedir as homedir8, tmpdir as tmpdir6 } from "node:os";
4367
4362
  var USER_HOME6 = (() => {
4368
4363
  try {
@@ -4371,15 +4366,15 @@ var USER_HOME6 = (() => {
4371
4366
  return tmpdir6();
4372
4367
  }
4373
4368
  })();
4374
- var REPORTS_DIR2 = join9(USER_HOME6, ".claude/reports");
4375
- var REPORTS_INDEX = join9(REPORTS_DIR2, "index.json");
4369
+ var REPORTS_DIR2 = join8(USER_HOME6, ".claude/reports");
4370
+ var REPORTS_INDEX = join8(REPORTS_DIR2, "index.json");
4376
4371
  var _OC_SID4 = "opencode-" + (process.pid || "x") + "-" + Date.now();
4377
4372
  var currentProjectFingerprint3 = "";
4378
4373
  var currentProjectName2 = "";
4379
4374
  function _handleStateCorruption5(path) {
4380
- const backupDir = join9(USER_HOME6, ".claude", ".backups");
4375
+ const backupDir = join8(USER_HOME6, ".claude", ".backups");
4381
4376
  mkdirSync6(backupDir, { recursive: true });
4382
- const backupPath = join9(backupDir, basename6(path) + ".corrupted." + Date.now());
4377
+ const backupPath = join8(backupDir, basename6(path) + ".corrupted." + Date.now());
4383
4378
  try {
4384
4379
  copyFileSync5(path, backupPath);
4385
4380
  } catch {
@@ -4449,7 +4444,7 @@ function _pruneReports() {
4449
4444
  continue;
4450
4445
  if (now - created > 90 * 24 * 3600 * 1e3) {
4451
4446
  try {
4452
- rmSync4(join9(REPORTS_DIR2, `${r.id}.json`));
4447
+ rmSync4(join8(REPORTS_DIR2, `${r.id}.json`));
4453
4448
  } catch {
4454
4449
  }
4455
4450
  continue;
@@ -4520,7 +4515,7 @@ function saveReport({ type = "manual", summary = "", findings = null, metrics =
4520
4515
  try {
4521
4516
  withFileLock(REPORTS_INDEX, () => {
4522
4517
  mkdirSync6(REPORTS_DIR2, { recursive: true });
4523
- writeFileSync6(join9(REPORTS_DIR2, `${id2}.json`), JSON.stringify(report, null, 2) + "\n");
4518
+ writeFileSync6(join8(REPORTS_DIR2, `${id2}.json`), JSON.stringify(report, null, 2) + "\n");
4524
4519
  const idx = reportsIndex();
4525
4520
  const _sum = (summary || "").slice(0, 80);
4526
4521
  idx.reports.push({ id: id2, type, project: report.meta.project, fingerprint: fp3, created: report.meta.created, summary: _sum });
@@ -4554,7 +4549,7 @@ function readReport(id2) {
4554
4549
  return null;
4555
4550
  if (!/^[\w-]+$/.test(String(id2)))
4556
4551
  return null;
4557
- const path = join9(REPORTS_DIR2, `${id2}.json`);
4552
+ const path = join8(REPORTS_DIR2, `${id2}.json`);
4558
4553
  try {
4559
4554
  if (!existsSync8(path))
4560
4555
  return null;
@@ -4566,7 +4561,7 @@ function readReport(id2) {
4566
4561
 
4567
4562
  // src/lib/credit-api.js
4568
4563
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync9 } from "node:fs";
4569
- import { join as join10 } from "node:path";
4564
+ import { join as join9 } from "node:path";
4570
4565
  function safeJsonParse4(raw) {
4571
4566
  try {
4572
4567
  return JSON.parse(raw);
@@ -4645,7 +4640,7 @@ function _cachedPct() {
4645
4640
  return null;
4646
4641
  let budget = 50;
4647
4642
  try {
4648
- const p = join10(USER_HOME2, ".claude/model-tiers.json");
4643
+ const p = join9(USER_HOME2, ".claude/model-tiers.json");
4649
4644
  if (existsSync9(p)) {
4650
4645
  const j = safeJsonParse4(readFileSync9(p, "utf-8"));
4651
4646
  if (j?.selection?.monthly_budget_usd)
@@ -4678,7 +4673,7 @@ function loadCredit() {
4678
4673
  return n;
4679
4674
  }
4680
4675
  try {
4681
- const f = join10(USER_HOME2, ".claude/credit-percent");
4676
+ const f = join9(USER_HOME2, ".claude/credit-percent");
4682
4677
  if (existsSync9(f)) {
4683
4678
  const n = parseInt(readFileSync9(f, "utf-8").trim(), 10);
4684
4679
  if (!isNaN(n))
@@ -4697,7 +4692,7 @@ function thinkingLevel(credit) {
4697
4692
  }
4698
4693
 
4699
4694
  // src/lib/trinity-tool.js
4700
- import { join as join11 } from "node:path";
4695
+ import { join as join10 } from "node:path";
4701
4696
  function createTrinityTool(deps) {
4702
4697
  return {
4703
4698
  description: "Control the vibeOS plugin and active model slot. Use action='status' to see current state. Use action='enable' or 'disable' to toggle the plugin (takes effect immediately, no restart needed). Use action='set' with slot='brain'|'medium'|'cheap' to switch model tiers (writes opencode.json \u2014 active immediately). Use action='rebuild' to auto-detect available models from all configured providers and reassign brain/medium/cheap slots. Use action='flow' with slot='on'|'off' to toggle flow enforcer, or action='flow' alone for audit. Use action='flow' with slot='enforce' and level='on'|'off' to toggle auto-extract TODOs. Use action='enforce' with slot='on'|'off' to toggle delegation enforcement (blocks direct writes/edits on brain tier). Use action='tdd' with slot='on'|'off' to toggle auto-create test skeletons. Use action='tdd' with slot='strict' and level='on'|'off' to toggle strict failing TODO test templates. Use action='tdd' alone for audit. Use action='project' to show per-project analytics and optimization suggestions. Use action='patterns' to inspect learned project patterns or slot='clear' to clear them. Use action='guard' to ensure AGENTS.md and README.md exist and stay current. Call this when the user says things like 'switch to medium', 'use cheap model', 'disable plugin', 'trinity status'.",
@@ -4952,7 +4947,7 @@ Lock is per-session (resets on restart).`;
4952
4947
  const ok = deps.writeSelection("tdd_enforce", slot === "on");
4953
4948
  return ok ? `\u2705 TDD enforcement ${slot === "on" ? "ENABLED (auto-create skeletons)" : "DISABLED (nudge only)"}` : `\u274C Failed to write model-tiers.json`;
4954
4949
  }
4955
- const stateFile = join11(deps.USER_HOME, ".claude/delegation-state.json");
4950
+ const stateFile = join10(deps.USER_HOME, ".claude/delegation-state.json");
4956
4951
  let enforced = 0;
4957
4952
  try {
4958
4953
  if (deps.existsSync(stateFile)) {
@@ -5332,7 +5327,7 @@ ${L.repeat(40)}`);
5332
5327
  }
5333
5328
  if (action === "diagnose") {
5334
5329
  const results = [];
5335
- const ocConfig = join11(deps.USER_HOME, ".config/opencode/opencode.json");
5330
+ const ocConfig = join10(deps.USER_HOME, ".config/opencode/opencode.json");
5336
5331
  const checks = [
5337
5332
  { path: deps.TIERS_FILE, label: "model-tiers.json" },
5338
5333
  { path: ocConfig, label: "opencode.json" },
@@ -5504,7 +5499,7 @@ ${L.repeat(40)}`);
5504
5499
  for (const r of idx.reports || []) {
5505
5500
  if (r.project !== name || r.fingerprint !== dstFp)
5506
5501
  continue;
5507
- const rf = join11(deps.REPORTS_DIR, `${r.id}.json`);
5502
+ const rf = join10(deps.REPORTS_DIR, `${r.id}.json`);
5508
5503
  try {
5509
5504
  if (!deps.existsSync(rf))
5510
5505
  continue;
@@ -5624,7 +5619,7 @@ ${L.repeat(40)}`);
5624
5619
 
5625
5620
  // src/lib/trinity-rebuild.js
5626
5621
  import { readFileSync as readFileSync10, existsSync as existsSync10 } from "node:fs";
5627
- import { join as join12 } from "node:path";
5622
+ import { join as join11 } from "node:path";
5628
5623
  var MODEL_RANK = { high: 3, mid: 2, budget: 1 };
5629
5624
  var OPENCODE_GO_CATALOG = [
5630
5625
  "deepseek/deepseek-v4-flash",
@@ -5828,16 +5823,16 @@ async function probeModel(modelId, auth) {
5828
5823
 
5829
5824
  // src/lib/hooks/footer.js
5830
5825
  import { readFileSync as readFileSync12 } from "node:fs";
5831
- import { join as join15 } from "node:path";
5826
+ import { join as join14 } from "node:path";
5832
5827
  import { homedir as homedir9, tmpdir as tmpdir7 } from "node:os";
5833
5828
 
5834
5829
  // src/lib/hooks/chat-transform.js
5835
5830
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync11, mkdirSync as mkdirSync7 } from "node:fs";
5836
- import { join as join14, basename as basename7 } from "node:path";
5831
+ import { join as join13, basename as basename7 } from "node:path";
5837
5832
  import { createHash as createHash4 } from "node:crypto";
5838
5833
 
5839
5834
  // src/lib/index-helpers.js
5840
- import { join as join13 } from "node:path";
5835
+ import { join as join12 } from "node:path";
5841
5836
  import { writeFileSync as writeFileSync8 } from "node:fs";
5842
5837
 
5843
5838
  // src/lib/text-compress.js
@@ -6172,7 +6167,7 @@ function recordSaving(tool2, reason, saveEst, meta = {}) {
6172
6167
  try {
6173
6168
  const sd = getSessionScratchpadDir();
6174
6169
  if (sd) {
6175
- const sp = join13(sd, "delegation-state-hint.txt");
6170
+ const sp = join12(sd, "delegation-state-hint.txt");
6176
6171
  try {
6177
6172
  writeFileSync8(sp, JSON.stringify({ sid, total_savings: s.lifetime.total_savings_usd, last_reason: reason }), "utf8");
6178
6173
  } catch {
@@ -6301,10 +6296,10 @@ function buildProjectBriefing(directory3) {
6301
6296
  return `[project memory] Active project: ${label}. Stay focused on the current repository and prefer the existing workflow.`;
6302
6297
  }
6303
6298
  function ensureProjectSkill(dir, fp3) {
6304
- const skillsDir = join14(dir, ".opencode", "skills");
6299
+ const skillsDir = join13(dir, ".opencode", "skills");
6305
6300
  const projectName = basename7(dir);
6306
- const skillDir = join14(skillsDir, projectName);
6307
- const skillPath = join14(skillDir, "SKILL.md");
6301
+ const skillDir = join13(skillsDir, projectName);
6302
+ const skillPath = join13(skillDir, "SKILL.md");
6308
6303
  if (existsSync11(skillPath)) {
6309
6304
  return { created: false, skipped: true, path: skillPath };
6310
6305
  }
@@ -6461,7 +6456,7 @@ var onMessagesTransform = async (_input, output) => {
6461
6456
  const hash = createHash4("sha256").update(`tool_result
6462
6457
  ${raw}
6463
6458
  `).digest("hex").slice(0, 16);
6464
- const fullPath = join14(getSessionScratchpadDir(), `${hash}.txt`);
6459
+ const fullPath = join13(getSessionScratchpadDir(), `${hash}.txt`);
6465
6460
  try {
6466
6461
  ensureSessionScratchpadDirs();
6467
6462
  if (!existsSync11(fullPath)) {
@@ -6733,14 +6728,14 @@ var USER_HOME7 = (() => {
6733
6728
  return tmpdir7();
6734
6729
  }
6735
6730
  })();
6736
- var STATE_FILE5 = join15(USER_HOME7, ".claude/delegation-state.json");
6737
- var SAVINGS_LEDGER_FILE2 = join15(USER_HOME7, ".claude/savings-ledger.jsonl");
6731
+ var STATE_FILE5 = join14(USER_HOME7, ".claude/delegation-state.json");
6732
+ var SAVINGS_LEDGER_FILE2 = join14(USER_HOME7, ".claude/savings-ledger.jsonl");
6738
6733
  var _prevOutputText = "";
6739
6734
  var _autoReportCount = 0;
6740
6735
  var textCompletePainted = /* @__PURE__ */ new Set();
6741
6736
  function loadSelection3() {
6742
6737
  try {
6743
- const raw = readFileSync12(join15(USER_HOME7, ".claude/model-tiers.json"), "utf-8");
6738
+ const raw = readFileSync12(join14(USER_HOME7, ".claude/model-tiers.json"), "utf-8");
6744
6739
  return safeJsonParse3(raw)?.selection || { active_slot: "medium", enabled: true, delegation_enforce: false, flow_enabled: false, flow_enforce: false, tdd_enforce: false, tdd_strict: false };
6745
6740
  } catch {
6746
6741
  return { active_slot: "medium", enabled: true, delegation_enforce: false, flow_enabled: false, flow_enforce: false, tdd_enforce: false, tdd_strict: false };
@@ -6818,6 +6813,10 @@ async function _appendFooter(input, output, directory3) {
6818
6813
  if (messageID && textCompletePainted.has(messageID))
6819
6814
  return;
6820
6815
  const text = typeof output?.text === "string" ? output.text : typeof output?.result === "string" ? output.result : typeof output?.content === "string" ? output.content : "";
6816
+ if (!text || text.length < 50) {
6817
+ if (messageID) textCompletePainted.add(messageID);
6818
+ return;
6819
+ }
6821
6820
  const { ltTasks, ltCache, ltCost, count, sesTasks, sesEdit, sesCredit, sesC7, sesQuota, sesCache, sesTaskDelegations, sesDuration, sesRatePerHour, sesTrend, sesToolBreakdown, sesModelTurns, quality_avg } = readLifetimeSavings2();
6822
6821
  const sessionSlot = loadSessionSlot(_OC_SID6);
6823
6822
  const slot = sessionSlot || loadSelection3().active_slot || "brain";
@@ -6980,7 +6979,7 @@ init_flow_enforcer();
6980
6979
 
6981
6980
  // src/lib/tdd-enforcer.js
6982
6981
  import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, appendFileSync as appendFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync8, statSync as statSync8, readdirSync as readdirSync2, rmSync as rmSync5, openSync as openSync4 } from "node:fs";
6983
- import { join as join16, dirname as dirname6 } from "node:path";
6982
+ import { join as join15, dirname as dirname6 } from "node:path";
6984
6983
  import { createHash as createHash5 } from "node:crypto";
6985
6984
 
6986
6985
  // src/utils/tdd-helpers.js
@@ -8019,7 +8018,7 @@ function _detectTestFramework() {
8019
8018
  let testExt = null;
8020
8019
  try {
8021
8020
  const root = directory || process.cwd();
8022
- const pkgPath = join16(root, "package.json");
8021
+ const pkgPath = join15(root, "package.json");
8023
8022
  if (existsSync12(pkgPath)) {
8024
8023
  const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
8025
8024
  const testScript = String(pkg?.scripts?.test || "");
@@ -8041,12 +8040,12 @@ function _detectTestFramework() {
8041
8040
  if (!framework) {
8042
8041
  const testDirs = ["src/tests", "tests", "test", "__tests__"];
8043
8042
  for (const td of testDirs) {
8044
- const dirPath = join16(root, td);
8043
+ const dirPath = join15(root, td);
8045
8044
  if (!existsSync12(dirPath))
8046
8045
  continue;
8047
8046
  const files = readdirSync2(dirPath).filter((f) => /\.test\./.test(f) || /\.spec\./.test(f));
8048
8047
  if (files.length > 0) {
8049
- const content = readFileSync13(join16(dirPath, files[0]), "utf-8");
8048
+ const content = readFileSync13(join15(dirPath, files[0]), "utf-8");
8050
8049
  if (/from\s+['"]node:test['"]/.test(content)) {
8051
8050
  framework = "node-test";
8052
8051
  testExt = files[0].split(".").pop();
@@ -8072,16 +8071,16 @@ function _detectTestFramework() {
8072
8071
  console.error(`[vibeOS] [tdd] detected test framework: ${framework || "default"} (ext: ${testExt || "match source"})`);
8073
8072
  return _detectedFramework;
8074
8073
  }
8075
- var ENFORCEMENT_LOCK_DIR = join16(USER_HOME2, ".claude/.enforcement-lock");
8074
+ var ENFORCEMENT_LOCK_DIR = join15(USER_HOME2, ".claude/.enforcement-lock");
8076
8075
  var LOCK_EXPIRE_MS = 3e4;
8077
- var ENFORCEMENT_COOLDOWN_FILE2 = join16(USER_HOME2, ".claude/.enforcement-cooldown.jsonl");
8076
+ var ENFORCEMENT_COOLDOWN_FILE2 = join15(USER_HOME2, ".claude/.enforcement-cooldown.jsonl");
8078
8077
  var COOLDOWN_MS = 6e4;
8079
8078
  var _enforcementCooldown = /* @__PURE__ */ new Set();
8080
8079
  function _acquireLock(testPath) {
8081
8080
  try {
8082
8081
  mkdirSync8(ENFORCEMENT_LOCK_DIR, { recursive: true });
8083
8082
  const hash = createHash5("sha256").update(testPath).digest("hex").slice(0, 16);
8084
- const lockPath = join16(ENFORCEMENT_LOCK_DIR, `${hash}.lock`);
8083
+ const lockPath = join15(ENFORCEMENT_LOCK_DIR, `${hash}.lock`);
8085
8084
  try {
8086
8085
  openSync4(lockPath, "wx");
8087
8086
  return true;
@@ -8109,7 +8108,7 @@ function _acquireLock(testPath) {
8109
8108
  function _releaseLock(testPath) {
8110
8109
  try {
8111
8110
  const hash = createHash5("sha256").update(testPath).digest("hex").slice(0, 16);
8112
- const lockPath = join16(ENFORCEMENT_LOCK_DIR, `${hash}.lock`);
8111
+ const lockPath = join15(ENFORCEMENT_LOCK_DIR, `${hash}.lock`);
8113
8112
  rmSync5(lockPath);
8114
8113
  } catch {
8115
8114
  }
@@ -8895,15 +8894,15 @@ var _mcpServerRuntime = null;
8895
8894
  var _mcpServerHooked = false;
8896
8895
  function _loadOpenCodeProviders() {
8897
8896
  try {
8898
- const cfg = _readOpenCodeConfigObject(join17(USER_HOME2, ".config", "opencode"));
8897
+ const cfg = _readOpenCodeConfigObject(join16(USER_HOME2, ".config", "opencode"));
8899
8898
  return cfg?.provider || {};
8900
8899
  } catch {
8901
8900
  return {};
8902
8901
  }
8903
8902
  }
8904
8903
  function _readOpenCodeConfigObject(dir) {
8905
- const jsonPath = join17(dir, "opencode.json");
8906
- const jsoncPath = join17(dir, "opencode.jsonc");
8904
+ const jsonPath = join16(dir, "opencode.json");
8905
+ const jsoncPath = join16(dir, "opencode.jsonc");
8907
8906
  if (existsSync15(jsonPath)) return safeJsonParse3(readFileSync15(jsonPath, "utf-8"));
8908
8907
  if (existsSync15(jsoncPath)) return _parseJsonc(readFileSync15(jsoncPath, "utf-8"));
8909
8908
  return {};
@@ -8931,9 +8930,9 @@ function _modelTier2(id2) {
8931
8930
  function backupFile(path, label) {
8932
8931
  try {
8933
8932
  if (!existsSync15(path)) return null;
8934
- const bkDir = join17(USER_HOME2, ".claude", ".backups");
8933
+ const bkDir = join16(USER_HOME2, ".claude", ".backups");
8935
8934
  mkdirSync10(bkDir, { recursive: true });
8936
- const bk = join17(bkDir, `${basename9(path)}.${label}.${Date.now()}.bak`);
8935
+ const bk = join16(bkDir, `${basename9(path)}.${label}.${Date.now()}.bak`);
8937
8936
  copyFileSync6(path, bk);
8938
8937
  return bk;
8939
8938
  } catch {
@@ -8942,7 +8941,7 @@ function backupFile(path, label) {
8942
8941
  }
8943
8942
  function readPackageVersion() {
8944
8943
  try {
8945
- const pkg = safeJsonParse3(readFileSync15(join17(process.cwd(), "package.json"), "utf-8"));
8944
+ const pkg = safeJsonParse3(readFileSync15(join16(process.cwd(), "package.json"), "utf-8"));
8946
8945
  return String(pkg?.version || "");
8947
8946
  } catch {
8948
8947
  return "";
@@ -9138,7 +9137,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
9138
9137
  setCurrentModel(readConfig(directory3));
9139
9138
  if (!currentModel) {
9140
9139
  const home = process.env.HOME || "";
9141
- if (home) setCurrentModel(readConfig(join17(home, ".config/opencode")));
9140
+ if (home) setCurrentModel(readConfig(join16(home, ".config/opencode")));
9142
9141
  }
9143
9142
  if (!currentModel) setCurrentModel(process?.env?.OPENCODE_MODEL || "");
9144
9143
  if (currentModel) {