vibeostheog 0.15.9 → 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,9 @@
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
+
1
7
  ## 0.15.9
2
8
  - fix: VIBEOS_API_TOKEN lookup from __dirname, ~/.claude/, ~/, cwd/
3
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.15.9",
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,33 +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();
3510
- } catch {
3511
- }
3512
- }
3513
- if (!_envToken2) {
3514
- try {
3515
- const env = readFileSync5(join6(homedir5(), ".claude", ".env.production"), "utf8");
3516
- const m = env.match(/^VIBEOS_API_TOKEN=(.+)$/m);
3517
- if (m) _envToken2 = m[1].trim();
3503
+ if (m) {
3504
+ _envToken = m[1].trim();
3505
+ break;
3506
+ }
3518
3507
  } catch {
3519
3508
  }
3520
3509
  }
3521
- var VIBEOS_API_TOKEN = process.env.VIBEOS_API_TOKEN || _envToken2 || "";
3510
+ var VIBEOS_API_TOKEN = process.env.VIBEOS_API_TOKEN || _envToken || "";
3522
3511
  var VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && !!VIBEOS_API_TOKEN;
3523
3512
  var _apiClient = null;
3524
3513
  var _apiFallbackMode = false;
@@ -3538,15 +3527,13 @@ function isApiFallback() {
3538
3527
  }
3539
3528
  async function remoteCall(method, args, fallbackFn) {
3540
3529
  if (!VIBEOS_API_ENABLED || _apiFallbackMode) {
3541
- if (fallbackFn)
3542
- return fallbackFn();
3530
+ if (fallbackFn) return fallbackFn();
3543
3531
  return null;
3544
3532
  }
3545
3533
  try {
3546
3534
  const client2 = getApiClient();
3547
3535
  if (!client2) {
3548
- if (fallbackFn)
3549
- return fallbackFn();
3536
+ if (fallbackFn) return fallbackFn();
3550
3537
  return null;
3551
3538
  }
3552
3539
  const result = await client2[method](...args);
@@ -3557,13 +3544,13 @@ async function remoteCall(method, args, fallbackFn) {
3557
3544
  if (!_apiFallbackMode) {
3558
3545
  _apiFallbackMode = true;
3559
3546
  _apiFallbackSince = (/* @__PURE__ */ new Date()).toISOString();
3560
- console.error(`[vibeOS] API fallback activated: ${err.message}`);
3547
+ console.error("[vibeOS] API fallback activated: " + err.message);
3561
3548
  }
3562
3549
  if (fallbackFn) {
3563
3550
  try {
3564
3551
  return fallbackFn();
3565
3552
  } catch (fe) {
3566
- console.error(`[vibeOS] fallback also failed: ${fe.message}`);
3553
+ console.error("[vibeOS] fallback also failed: " + fe.message);
3567
3554
  }
3568
3555
  }
3569
3556
  return null;
@@ -3775,11 +3762,11 @@ var USER_HOME4 = (() => {
3775
3762
  return tmpdir4();
3776
3763
  }
3777
3764
  })();
3778
- var FILE_LOCK_DIR3 = join7(USER_HOME4, ".claude/.vibeOS-locks");
3779
- var BLACKBOX_STATE_FILE2 = join7(USER_HOME4, ".claude/blackbox-state.json");
3780
- var GLOBAL_LEARNING_FILE2 = join7(USER_HOME4, ".claude/global-learning.json");
3781
- var STATE_FILE3 = join7(USER_HOME4, ".claude/delegation-state.json");
3782
- 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");
3783
3770
  var DFLT_GL2 = { exploratory_words: {}, task_first_words: {}, updatedAt: null };
3784
3771
  var _blackboxTracker = null;
3785
3772
  var _OC_SID2 = "opencode-" + (process.pid || "x") + "-" + Date.now();
@@ -3794,14 +3781,14 @@ var WARN_MAX_PER_SESSION = 3;
3794
3781
  var WARN_COALESCE_THRESHOLD = 10;
3795
3782
  var warnCoalesceCounters = /* @__PURE__ */ new Map();
3796
3783
  function _handleStateCorruption4(path) {
3797
- const backupDir = join7(USER_HOME4, ".claude", ".backups");
3784
+ const backupDir = join6(USER_HOME4, ".claude", ".backups");
3798
3785
  mkdirSync5(backupDir, { recursive: true });
3799
- const backupPath = join7(backupDir, basename5(path) + ".corrupted." + Date.now());
3786
+ const backupPath = join6(backupDir, basename5(path) + ".corrupted." + Date.now());
3800
3787
  try {
3801
3788
  copyFileSync4(path, backupPath);
3802
3789
  } catch {
3803
3790
  }
3804
- const logPath = join7(USER_HOME4, ".claude", ".state-corruption-log.jsonl");
3791
+ const logPath = join6(USER_HOME4, ".claude", ".state-corruption-log.jsonl");
3805
3792
  try {
3806
3793
  appendFileSync5(logPath, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), path, backup: backupPath }) + "\n");
3807
3794
  } catch {
@@ -3809,7 +3796,7 @@ function _handleStateCorruption4(path) {
3809
3796
  }
3810
3797
  function _lockPathFor3(filePath) {
3811
3798
  const hash = createHash3("sha1").update(String(filePath || "")).digest("hex");
3812
- return join7(FILE_LOCK_DIR3, `${hash}.lock`);
3799
+ return join6(FILE_LOCK_DIR3, `${hash}.lock`);
3813
3800
  }
3814
3801
  function withFileLock3(filePath, fn, opts = {}) {
3815
3802
  const staleMs = Number(opts.staleMs || 3e4);
@@ -3872,7 +3859,7 @@ function readJsonOrEmpty2(filePath) {
3872
3859
  }
3873
3860
  function loadTrinityModels() {
3874
3861
  try {
3875
- const p = join7(USER_HOME4, ".claude/model-tiers.json");
3862
+ const p = join6(USER_HOME4, ".claude/model-tiers.json");
3876
3863
  if (!existsSync6(p))
3877
3864
  return { brain: "", cheap: "", medium: "" };
3878
3865
  const j = safeJsonParse3(readFileSync6(p, "utf-8"));
@@ -4116,9 +4103,9 @@ function saveProjectState2(state) {
4116
4103
  function detectTechStack2(dir) {
4117
4104
  const stacks = [];
4118
4105
  try {
4119
- const pkg = safeJsonParse3(readFileSync6(join7(dir, "package.json"), "utf-8"));
4106
+ const pkg = safeJsonParse3(readFileSync6(join6(dir, "package.json"), "utf-8"));
4120
4107
  if (pkg) {
4121
- 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")))
4122
4109
  stacks.push("typescript");
4123
4110
  if (pkg.dependencies?.react || pkg.devDependencies?.react)
4124
4111
  stacks.push("react");
@@ -4127,21 +4114,21 @@ function detectTechStack2(dir) {
4127
4114
  } catch {
4128
4115
  }
4129
4116
  try {
4130
- if (existsSync6(join7(dir, "Cargo.toml")))
4117
+ if (existsSync6(join6(dir, "Cargo.toml")))
4131
4118
  stacks.push("rust");
4132
4119
  } catch {
4133
4120
  }
4134
4121
  try {
4135
- if (existsSync6(join7(dir, "go.mod")))
4122
+ if (existsSync6(join6(dir, "go.mod")))
4136
4123
  stacks.push("go");
4137
4124
  } catch {
4138
4125
  }
4139
4126
  try {
4140
- if (existsSync6(join7(dir, "requirements.txt")))
4127
+ if (existsSync6(join6(dir, "requirements.txt")))
4141
4128
  stacks.push("python");
4142
- if (existsSync6(join7(dir, "setup.py")))
4129
+ if (existsSync6(join6(dir, "setup.py")))
4143
4130
  stacks.push("python");
4144
- if (existsSync6(join7(dir, "pyproject.toml")))
4131
+ if (existsSync6(join6(dir, "pyproject.toml")))
4145
4132
  stacks.push("python");
4146
4133
  } catch {
4147
4134
  }
@@ -4250,7 +4237,7 @@ function saveOptimizationMode(mode) {
4250
4237
 
4251
4238
  // src/lib/research-audit.js
4252
4239
  import { readFileSync as readFileSync7, existsSync as existsSync7 } from "node:fs";
4253
- import { join as join8 } from "node:path";
4240
+ import { join as join7 } from "node:path";
4254
4241
  import { homedir as homedir7, tmpdir as tmpdir5 } from "node:os";
4255
4242
  var USER_HOME5 = (() => {
4256
4243
  try {
@@ -4260,19 +4247,19 @@ var USER_HOME5 = (() => {
4260
4247
  }
4261
4248
  })();
4262
4249
  var _OC_SID3 = "opencode-" + (process.pid || "x") + "-" + Date.now();
4263
- var SCRATCHPAD_ROOT2 = join8(USER_HOME5, ".claude/scratch");
4264
- var SCRATCHPAD_GLOBAL_DIR2 = join8(SCRATCHPAD_ROOT2, "by-hash");
4265
- var SCRATCHPAD_SESSIONS_DIR2 = join8(SCRATCHPAD_ROOT2, "sessions");
4266
- 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");
4267
4254
  var currentModel2 = null;
4268
4255
  function getSessionRoot2() {
4269
- return join8(SCRATCHPAD_SESSIONS_DIR2, _OC_SID3);
4256
+ return join7(SCRATCHPAD_SESSIONS_DIR2, _OC_SID3);
4270
4257
  }
4271
4258
  function getSessionScratchpadDir2() {
4272
- return join8(getSessionRoot2(), "by-hash");
4259
+ return join7(getSessionRoot2(), "by-hash");
4273
4260
  }
4274
4261
  function getGlobalIndexPath2() {
4275
- return join8(SCRATCHPAD_ROOT2, "index.jsonl");
4262
+ return join7(SCRATCHPAD_ROOT2, "index.jsonl");
4276
4263
  }
4277
4264
  var FETCH_TOOLS = /* @__PURE__ */ new Set(["WebFetch", "WebSearch", "webfetch", "websearch"]);
4278
4265
  function researchAudit({ hours = 24, session: sessionFilter } = {}) {
@@ -4295,8 +4282,8 @@ function researchAudit({ hours = 24, session: sessionFilter } = {}) {
4295
4282
  report.totalFetches++;
4296
4283
  report.totalBytes += e.size || 0;
4297
4284
  const hash = e.hash;
4298
- const summaryPathSession = join8(getSessionScratchpadDir2(), hash + ".summary.txt");
4299
- 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");
4300
4287
  const summaryPath = existsSync7(summaryPathSession) ? summaryPathSession : summaryPathGlobal;
4301
4288
  if (existsSync7(summaryPath)) {
4302
4289
  const summary = readFileSync7(summaryPath, "utf-8").slice(0, 200);
@@ -4370,7 +4357,7 @@ function researchAudit({ hours = 24, session: sessionFilter } = {}) {
4370
4357
 
4371
4358
  // src/lib/reporting.js
4372
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";
4373
- import { join as join9, basename as basename6 } from "node:path";
4360
+ import { join as join8, basename as basename6 } from "node:path";
4374
4361
  import { homedir as homedir8, tmpdir as tmpdir6 } from "node:os";
4375
4362
  var USER_HOME6 = (() => {
4376
4363
  try {
@@ -4379,15 +4366,15 @@ var USER_HOME6 = (() => {
4379
4366
  return tmpdir6();
4380
4367
  }
4381
4368
  })();
4382
- var REPORTS_DIR2 = join9(USER_HOME6, ".claude/reports");
4383
- 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");
4384
4371
  var _OC_SID4 = "opencode-" + (process.pid || "x") + "-" + Date.now();
4385
4372
  var currentProjectFingerprint3 = "";
4386
4373
  var currentProjectName2 = "";
4387
4374
  function _handleStateCorruption5(path) {
4388
- const backupDir = join9(USER_HOME6, ".claude", ".backups");
4375
+ const backupDir = join8(USER_HOME6, ".claude", ".backups");
4389
4376
  mkdirSync6(backupDir, { recursive: true });
4390
- const backupPath = join9(backupDir, basename6(path) + ".corrupted." + Date.now());
4377
+ const backupPath = join8(backupDir, basename6(path) + ".corrupted." + Date.now());
4391
4378
  try {
4392
4379
  copyFileSync5(path, backupPath);
4393
4380
  } catch {
@@ -4457,7 +4444,7 @@ function _pruneReports() {
4457
4444
  continue;
4458
4445
  if (now - created > 90 * 24 * 3600 * 1e3) {
4459
4446
  try {
4460
- rmSync4(join9(REPORTS_DIR2, `${r.id}.json`));
4447
+ rmSync4(join8(REPORTS_DIR2, `${r.id}.json`));
4461
4448
  } catch {
4462
4449
  }
4463
4450
  continue;
@@ -4528,7 +4515,7 @@ function saveReport({ type = "manual", summary = "", findings = null, metrics =
4528
4515
  try {
4529
4516
  withFileLock(REPORTS_INDEX, () => {
4530
4517
  mkdirSync6(REPORTS_DIR2, { recursive: true });
4531
- 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");
4532
4519
  const idx = reportsIndex();
4533
4520
  const _sum = (summary || "").slice(0, 80);
4534
4521
  idx.reports.push({ id: id2, type, project: report.meta.project, fingerprint: fp3, created: report.meta.created, summary: _sum });
@@ -4562,7 +4549,7 @@ function readReport(id2) {
4562
4549
  return null;
4563
4550
  if (!/^[\w-]+$/.test(String(id2)))
4564
4551
  return null;
4565
- const path = join9(REPORTS_DIR2, `${id2}.json`);
4552
+ const path = join8(REPORTS_DIR2, `${id2}.json`);
4566
4553
  try {
4567
4554
  if (!existsSync8(path))
4568
4555
  return null;
@@ -4574,7 +4561,7 @@ function readReport(id2) {
4574
4561
 
4575
4562
  // src/lib/credit-api.js
4576
4563
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync9 } from "node:fs";
4577
- import { join as join10 } from "node:path";
4564
+ import { join as join9 } from "node:path";
4578
4565
  function safeJsonParse4(raw) {
4579
4566
  try {
4580
4567
  return JSON.parse(raw);
@@ -4653,7 +4640,7 @@ function _cachedPct() {
4653
4640
  return null;
4654
4641
  let budget = 50;
4655
4642
  try {
4656
- const p = join10(USER_HOME2, ".claude/model-tiers.json");
4643
+ const p = join9(USER_HOME2, ".claude/model-tiers.json");
4657
4644
  if (existsSync9(p)) {
4658
4645
  const j = safeJsonParse4(readFileSync9(p, "utf-8"));
4659
4646
  if (j?.selection?.monthly_budget_usd)
@@ -4686,7 +4673,7 @@ function loadCredit() {
4686
4673
  return n;
4687
4674
  }
4688
4675
  try {
4689
- const f = join10(USER_HOME2, ".claude/credit-percent");
4676
+ const f = join9(USER_HOME2, ".claude/credit-percent");
4690
4677
  if (existsSync9(f)) {
4691
4678
  const n = parseInt(readFileSync9(f, "utf-8").trim(), 10);
4692
4679
  if (!isNaN(n))
@@ -4705,7 +4692,7 @@ function thinkingLevel(credit) {
4705
4692
  }
4706
4693
 
4707
4694
  // src/lib/trinity-tool.js
4708
- import { join as join11 } from "node:path";
4695
+ import { join as join10 } from "node:path";
4709
4696
  function createTrinityTool(deps) {
4710
4697
  return {
4711
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'.",
@@ -4960,7 +4947,7 @@ Lock is per-session (resets on restart).`;
4960
4947
  const ok = deps.writeSelection("tdd_enforce", slot === "on");
4961
4948
  return ok ? `\u2705 TDD enforcement ${slot === "on" ? "ENABLED (auto-create skeletons)" : "DISABLED (nudge only)"}` : `\u274C Failed to write model-tiers.json`;
4962
4949
  }
4963
- const stateFile = join11(deps.USER_HOME, ".claude/delegation-state.json");
4950
+ const stateFile = join10(deps.USER_HOME, ".claude/delegation-state.json");
4964
4951
  let enforced = 0;
4965
4952
  try {
4966
4953
  if (deps.existsSync(stateFile)) {
@@ -5340,7 +5327,7 @@ ${L.repeat(40)}`);
5340
5327
  }
5341
5328
  if (action === "diagnose") {
5342
5329
  const results = [];
5343
- const ocConfig = join11(deps.USER_HOME, ".config/opencode/opencode.json");
5330
+ const ocConfig = join10(deps.USER_HOME, ".config/opencode/opencode.json");
5344
5331
  const checks = [
5345
5332
  { path: deps.TIERS_FILE, label: "model-tiers.json" },
5346
5333
  { path: ocConfig, label: "opencode.json" },
@@ -5512,7 +5499,7 @@ ${L.repeat(40)}`);
5512
5499
  for (const r of idx.reports || []) {
5513
5500
  if (r.project !== name || r.fingerprint !== dstFp)
5514
5501
  continue;
5515
- const rf = join11(deps.REPORTS_DIR, `${r.id}.json`);
5502
+ const rf = join10(deps.REPORTS_DIR, `${r.id}.json`);
5516
5503
  try {
5517
5504
  if (!deps.existsSync(rf))
5518
5505
  continue;
@@ -5632,7 +5619,7 @@ ${L.repeat(40)}`);
5632
5619
 
5633
5620
  // src/lib/trinity-rebuild.js
5634
5621
  import { readFileSync as readFileSync10, existsSync as existsSync10 } from "node:fs";
5635
- import { join as join12 } from "node:path";
5622
+ import { join as join11 } from "node:path";
5636
5623
  var MODEL_RANK = { high: 3, mid: 2, budget: 1 };
5637
5624
  var OPENCODE_GO_CATALOG = [
5638
5625
  "deepseek/deepseek-v4-flash",
@@ -5836,16 +5823,16 @@ async function probeModel(modelId, auth) {
5836
5823
 
5837
5824
  // src/lib/hooks/footer.js
5838
5825
  import { readFileSync as readFileSync12 } from "node:fs";
5839
- import { join as join15 } from "node:path";
5826
+ import { join as join14 } from "node:path";
5840
5827
  import { homedir as homedir9, tmpdir as tmpdir7 } from "node:os";
5841
5828
 
5842
5829
  // src/lib/hooks/chat-transform.js
5843
5830
  import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync11, mkdirSync as mkdirSync7 } from "node:fs";
5844
- import { join as join14, basename as basename7 } from "node:path";
5831
+ import { join as join13, basename as basename7 } from "node:path";
5845
5832
  import { createHash as createHash4 } from "node:crypto";
5846
5833
 
5847
5834
  // src/lib/index-helpers.js
5848
- import { join as join13 } from "node:path";
5835
+ import { join as join12 } from "node:path";
5849
5836
  import { writeFileSync as writeFileSync8 } from "node:fs";
5850
5837
 
5851
5838
  // src/lib/text-compress.js
@@ -6180,7 +6167,7 @@ function recordSaving(tool2, reason, saveEst, meta = {}) {
6180
6167
  try {
6181
6168
  const sd = getSessionScratchpadDir();
6182
6169
  if (sd) {
6183
- const sp = join13(sd, "delegation-state-hint.txt");
6170
+ const sp = join12(sd, "delegation-state-hint.txt");
6184
6171
  try {
6185
6172
  writeFileSync8(sp, JSON.stringify({ sid, total_savings: s.lifetime.total_savings_usd, last_reason: reason }), "utf8");
6186
6173
  } catch {
@@ -6309,10 +6296,10 @@ function buildProjectBriefing(directory3) {
6309
6296
  return `[project memory] Active project: ${label}. Stay focused on the current repository and prefer the existing workflow.`;
6310
6297
  }
6311
6298
  function ensureProjectSkill(dir, fp3) {
6312
- const skillsDir = join14(dir, ".opencode", "skills");
6299
+ const skillsDir = join13(dir, ".opencode", "skills");
6313
6300
  const projectName = basename7(dir);
6314
- const skillDir = join14(skillsDir, projectName);
6315
- const skillPath = join14(skillDir, "SKILL.md");
6301
+ const skillDir = join13(skillsDir, projectName);
6302
+ const skillPath = join13(skillDir, "SKILL.md");
6316
6303
  if (existsSync11(skillPath)) {
6317
6304
  return { created: false, skipped: true, path: skillPath };
6318
6305
  }
@@ -6469,7 +6456,7 @@ var onMessagesTransform = async (_input, output) => {
6469
6456
  const hash = createHash4("sha256").update(`tool_result
6470
6457
  ${raw}
6471
6458
  `).digest("hex").slice(0, 16);
6472
- const fullPath = join14(getSessionScratchpadDir(), `${hash}.txt`);
6459
+ const fullPath = join13(getSessionScratchpadDir(), `${hash}.txt`);
6473
6460
  try {
6474
6461
  ensureSessionScratchpadDirs();
6475
6462
  if (!existsSync11(fullPath)) {
@@ -6741,14 +6728,14 @@ var USER_HOME7 = (() => {
6741
6728
  return tmpdir7();
6742
6729
  }
6743
6730
  })();
6744
- var STATE_FILE5 = join15(USER_HOME7, ".claude/delegation-state.json");
6745
- 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");
6746
6733
  var _prevOutputText = "";
6747
6734
  var _autoReportCount = 0;
6748
6735
  var textCompletePainted = /* @__PURE__ */ new Set();
6749
6736
  function loadSelection3() {
6750
6737
  try {
6751
- 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");
6752
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 };
6753
6740
  } catch {
6754
6741
  return { active_slot: "medium", enabled: true, delegation_enforce: false, flow_enabled: false, flow_enforce: false, tdd_enforce: false, tdd_strict: false };
@@ -6826,6 +6813,10 @@ async function _appendFooter(input, output, directory3) {
6826
6813
  if (messageID && textCompletePainted.has(messageID))
6827
6814
  return;
6828
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
+ }
6829
6820
  const { ltTasks, ltCache, ltCost, count, sesTasks, sesEdit, sesCredit, sesC7, sesQuota, sesCache, sesTaskDelegations, sesDuration, sesRatePerHour, sesTrend, sesToolBreakdown, sesModelTurns, quality_avg } = readLifetimeSavings2();
6830
6821
  const sessionSlot = loadSessionSlot(_OC_SID6);
6831
6822
  const slot = sessionSlot || loadSelection3().active_slot || "brain";
@@ -6988,7 +6979,7 @@ init_flow_enforcer();
6988
6979
 
6989
6980
  // src/lib/tdd-enforcer.js
6990
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";
6991
- import { join as join16, dirname as dirname6 } from "node:path";
6982
+ import { join as join15, dirname as dirname6 } from "node:path";
6992
6983
  import { createHash as createHash5 } from "node:crypto";
6993
6984
 
6994
6985
  // src/utils/tdd-helpers.js
@@ -8027,7 +8018,7 @@ function _detectTestFramework() {
8027
8018
  let testExt = null;
8028
8019
  try {
8029
8020
  const root = directory || process.cwd();
8030
- const pkgPath = join16(root, "package.json");
8021
+ const pkgPath = join15(root, "package.json");
8031
8022
  if (existsSync12(pkgPath)) {
8032
8023
  const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
8033
8024
  const testScript = String(pkg?.scripts?.test || "");
@@ -8049,12 +8040,12 @@ function _detectTestFramework() {
8049
8040
  if (!framework) {
8050
8041
  const testDirs = ["src/tests", "tests", "test", "__tests__"];
8051
8042
  for (const td of testDirs) {
8052
- const dirPath = join16(root, td);
8043
+ const dirPath = join15(root, td);
8053
8044
  if (!existsSync12(dirPath))
8054
8045
  continue;
8055
8046
  const files = readdirSync2(dirPath).filter((f) => /\.test\./.test(f) || /\.spec\./.test(f));
8056
8047
  if (files.length > 0) {
8057
- const content = readFileSync13(join16(dirPath, files[0]), "utf-8");
8048
+ const content = readFileSync13(join15(dirPath, files[0]), "utf-8");
8058
8049
  if (/from\s+['"]node:test['"]/.test(content)) {
8059
8050
  framework = "node-test";
8060
8051
  testExt = files[0].split(".").pop();
@@ -8080,16 +8071,16 @@ function _detectTestFramework() {
8080
8071
  console.error(`[vibeOS] [tdd] detected test framework: ${framework || "default"} (ext: ${testExt || "match source"})`);
8081
8072
  return _detectedFramework;
8082
8073
  }
8083
- var ENFORCEMENT_LOCK_DIR = join16(USER_HOME2, ".claude/.enforcement-lock");
8074
+ var ENFORCEMENT_LOCK_DIR = join15(USER_HOME2, ".claude/.enforcement-lock");
8084
8075
  var LOCK_EXPIRE_MS = 3e4;
8085
- var ENFORCEMENT_COOLDOWN_FILE2 = join16(USER_HOME2, ".claude/.enforcement-cooldown.jsonl");
8076
+ var ENFORCEMENT_COOLDOWN_FILE2 = join15(USER_HOME2, ".claude/.enforcement-cooldown.jsonl");
8086
8077
  var COOLDOWN_MS = 6e4;
8087
8078
  var _enforcementCooldown = /* @__PURE__ */ new Set();
8088
8079
  function _acquireLock(testPath) {
8089
8080
  try {
8090
8081
  mkdirSync8(ENFORCEMENT_LOCK_DIR, { recursive: true });
8091
8082
  const hash = createHash5("sha256").update(testPath).digest("hex").slice(0, 16);
8092
- const lockPath = join16(ENFORCEMENT_LOCK_DIR, `${hash}.lock`);
8083
+ const lockPath = join15(ENFORCEMENT_LOCK_DIR, `${hash}.lock`);
8093
8084
  try {
8094
8085
  openSync4(lockPath, "wx");
8095
8086
  return true;
@@ -8117,7 +8108,7 @@ function _acquireLock(testPath) {
8117
8108
  function _releaseLock(testPath) {
8118
8109
  try {
8119
8110
  const hash = createHash5("sha256").update(testPath).digest("hex").slice(0, 16);
8120
- const lockPath = join16(ENFORCEMENT_LOCK_DIR, `${hash}.lock`);
8111
+ const lockPath = join15(ENFORCEMENT_LOCK_DIR, `${hash}.lock`);
8121
8112
  rmSync5(lockPath);
8122
8113
  } catch {
8123
8114
  }
@@ -8903,15 +8894,15 @@ var _mcpServerRuntime = null;
8903
8894
  var _mcpServerHooked = false;
8904
8895
  function _loadOpenCodeProviders() {
8905
8896
  try {
8906
- const cfg = _readOpenCodeConfigObject(join17(USER_HOME2, ".config", "opencode"));
8897
+ const cfg = _readOpenCodeConfigObject(join16(USER_HOME2, ".config", "opencode"));
8907
8898
  return cfg?.provider || {};
8908
8899
  } catch {
8909
8900
  return {};
8910
8901
  }
8911
8902
  }
8912
8903
  function _readOpenCodeConfigObject(dir) {
8913
- const jsonPath = join17(dir, "opencode.json");
8914
- const jsoncPath = join17(dir, "opencode.jsonc");
8904
+ const jsonPath = join16(dir, "opencode.json");
8905
+ const jsoncPath = join16(dir, "opencode.jsonc");
8915
8906
  if (existsSync15(jsonPath)) return safeJsonParse3(readFileSync15(jsonPath, "utf-8"));
8916
8907
  if (existsSync15(jsoncPath)) return _parseJsonc(readFileSync15(jsoncPath, "utf-8"));
8917
8908
  return {};
@@ -8939,9 +8930,9 @@ function _modelTier2(id2) {
8939
8930
  function backupFile(path, label) {
8940
8931
  try {
8941
8932
  if (!existsSync15(path)) return null;
8942
- const bkDir = join17(USER_HOME2, ".claude", ".backups");
8933
+ const bkDir = join16(USER_HOME2, ".claude", ".backups");
8943
8934
  mkdirSync10(bkDir, { recursive: true });
8944
- const bk = join17(bkDir, `${basename9(path)}.${label}.${Date.now()}.bak`);
8935
+ const bk = join16(bkDir, `${basename9(path)}.${label}.${Date.now()}.bak`);
8945
8936
  copyFileSync6(path, bk);
8946
8937
  return bk;
8947
8938
  } catch {
@@ -8950,7 +8941,7 @@ function backupFile(path, label) {
8950
8941
  }
8951
8942
  function readPackageVersion() {
8952
8943
  try {
8953
- const pkg = safeJsonParse3(readFileSync15(join17(process.cwd(), "package.json"), "utf-8"));
8944
+ const pkg = safeJsonParse3(readFileSync15(join16(process.cwd(), "package.json"), "utf-8"));
8954
8945
  return String(pkg?.version || "");
8955
8946
  } catch {
8956
8947
  return "";
@@ -9146,7 +9137,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
9146
9137
  setCurrentModel(readConfig(directory3));
9147
9138
  if (!currentModel) {
9148
9139
  const home = process.env.HOME || "";
9149
- if (home) setCurrentModel(readConfig(join17(home, ".config/opencode")));
9140
+ if (home) setCurrentModel(readConfig(join16(home, ".config/opencode")));
9150
9141
  }
9151
9142
  if (!currentModel) setCurrentModel(process?.env?.OPENCODE_MODEL || "");
9152
9143
  if (currentModel) {