vibeostheog 0.24.16 → 0.24.18

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,8 @@
1
+ ## 0.24.18
2
+ - fix: keep Return codename through 0.24 patch releases (#143)
3
+ - fix: preserve live metrics context in reports (#137)
4
+
5
+
1
6
  ## 0.24.16
2
7
  - fix: serialize model tiers writes
3
8
  - test: add concurrent tiers write regression
package/dist/vibeOS.js CHANGED
@@ -587,7 +587,7 @@ var init_meta_controller = __esm({
587
587
  });
588
588
 
589
589
  // src/vibeOS-lib/blackbox/pivot-cache.js
590
- import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
590
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
591
591
  import { join as join6, dirname as dirname6 } from "node:path";
592
592
  import { homedir as homedir5 } from "node:os";
593
593
  var PivotCache;
@@ -625,7 +625,7 @@ var init_pivot_cache = __esm({
625
625
  const p = this._storePath();
626
626
  const dir = dirname6(p);
627
627
  if (!existsSync7(dir))
628
- mkdirSync6(dir, { recursive: true });
628
+ mkdirSync5(dir, { recursive: true });
629
629
  writeFileSync6(p, JSON.stringify(this.store, null, 2), "utf-8");
630
630
  } catch {
631
631
  }
@@ -796,7 +796,7 @@ __export(vibemax_exports, {
796
796
  vibemaxPipeline: () => vibemaxPipeline,
797
797
  vibemaxSelectMode: () => vibemaxSelectMode
798
798
  });
799
- import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
799
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
800
800
  import { resolve as resolve2, dirname as dirname7 } from "node:path";
801
801
  import { fileURLToPath as fileURLToPath4 } from "node:url";
802
802
  function fallback(sr, text) {
@@ -1078,7 +1078,7 @@ function loadVibeMaXModel() {
1078
1078
  return null;
1079
1079
  }
1080
1080
  function saveVibeMaXModel(model) {
1081
- mkdirSync7(dirname7(MODEL_PATH), { recursive: true });
1081
+ mkdirSync6(dirname7(MODEL_PATH), { recursive: true });
1082
1082
  writeFileSync7(MODEL_PATH, JSON.stringify(model, null, 2) + "\n", "utf-8");
1083
1083
  }
1084
1084
  function getVibeMaXModelMeta() {
@@ -1104,8 +1104,8 @@ var init_vibemax = __esm({
1104
1104
 
1105
1105
  // src/index.ts
1106
1106
  init_flow_enforcer();
1107
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync15, existsSync as existsSync18, mkdirSync as mkdirSync14, copyFileSync as copyFileSync5, renameSync as renameSync6 } from "node:fs";
1108
- import { join as join18, dirname as dirname13, basename as basename8 } from "node:path";
1107
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync15, existsSync as existsSync18, mkdirSync as mkdirSync13, copyFileSync as copyFileSync2, renameSync as renameSync6 } from "node:fs";
1108
+ import { join as join18, dirname as dirname13, basename as basename5 } from "node:path";
1109
1109
 
1110
1110
  // src/vibeOS-lib/session-metrics.js
1111
1111
  function formatDuration(totalSeconds) {
@@ -2354,22 +2354,22 @@ async function remoteCall(method, args, fallbackFn) {
2354
2354
  }
2355
2355
 
2356
2356
  // src/lib/pricing.js
2357
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, appendFileSync as appendFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync5, statSync as statSync5, copyFileSync as copyFileSync3, renameSync as renameSync4, openSync as openSync2, closeSync as closeSync2, rmSync as rmSync3, readdirSync as readdirSync2 } from "node:fs";
2358
- import { join as join5, dirname as dirname5, basename as basename4, resolve } from "node:path";
2357
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4, statSync as statSync5, renameSync as renameSync4, openSync as openSync2, closeSync as closeSync2, rmSync as rmSync3, readdirSync as readdirSync2 } from "node:fs";
2358
+ import { join as join5, dirname as dirname5, resolve } from "node:path";
2359
2359
  import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
2360
2360
  import { createHash as createHash2 } from "node:crypto";
2361
2361
 
2362
2362
  // src/lib/state.js
2363
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, appendFileSync as appendFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync4, statSync as statSync4, readdirSync, openSync, readSync, closeSync, rmSync as rmSync2, copyFileSync as copyFileSync2, renameSync as renameSync3 } from "node:fs";
2364
- import { join as join4, dirname as dirname4, basename as basename3 } from "node:path";
2363
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, appendFileSync as appendFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync3, statSync as statSync4, readdirSync, openSync, readSync, closeSync, rmSync as rmSync2, copyFileSync, renameSync as renameSync3 } from "node:fs";
2364
+ import { join as join4, dirname as dirname4, basename as basename2 } from "node:path";
2365
2365
  import { spawn } from "node:child_process";
2366
2366
  import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
2367
2367
  import { createHash } from "node:crypto";
2368
2368
  import { AsyncLocalStorage } from "node:async_hooks";
2369
2369
 
2370
2370
  // src/lib/selection-manager.js
2371
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, statSync as statSync3, copyFileSync, renameSync as renameSync2 } from "node:fs";
2372
- import { join as join3, basename } from "node:path";
2371
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, statSync as statSync3, renameSync as renameSync2 } from "node:fs";
2372
+ import { join as join3 } from "node:path";
2373
2373
  import { homedir as homedir2, tmpdir } from "node:os";
2374
2374
  var USER_HOME = (() => {
2375
2375
  try {
@@ -2381,20 +2381,6 @@ var USER_HOME = (() => {
2381
2381
  function getVibeOSHome2() {
2382
2382
  return process.env.VIBEOS_HOME || join3(process.env.HOME || homedir2(), ".claude");
2383
2383
  }
2384
- function _handleStateCorruption(path) {
2385
- const backupDir = join3(getVibeOSHome2(), ".backups");
2386
- mkdirSync3(backupDir, { recursive: true });
2387
- const backupPath = join3(backupDir, basename(path) + ".corrupted." + Date.now());
2388
- try {
2389
- copyFileSync(path, backupPath);
2390
- } catch {
2391
- }
2392
- const logPath = join3(getVibeOSHome2(), ".state-corruption-log.jsonl");
2393
- try {
2394
- appendFileSync2(logPath, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), path, backup: backupPath }) + "\n");
2395
- } catch {
2396
- }
2397
- }
2398
2384
  function safeJsonParse2(raw) {
2399
2385
  if (raw == null || raw === "")
2400
2386
  return null;
@@ -2537,7 +2523,7 @@ function writeSessionOptMode(sid, mode) {
2537
2523
  }
2538
2524
 
2539
2525
  // src/lib/pattern-helpers.js
2540
- import { relative, basename as basename2 } from "node:path";
2526
+ import { relative, basename } from "node:path";
2541
2527
  function normalizeObservedPath(filePath, directory3) {
2542
2528
  if (!filePath || typeof filePath !== "string")
2543
2529
  return "unknown";
@@ -2558,7 +2544,7 @@ function normalizeObservedPath(filePath, directory3) {
2558
2544
  return `src/*.${m[1].toLowerCase()}`;
2559
2545
  if (p.startsWith("tests/") && m)
2560
2546
  return `tests/*.${m[1].toLowerCase()}`;
2561
- return basename2(p) || "unknown";
2547
+ return basename(p) || "unknown";
2562
2548
  }
2563
2549
  function commandFamily(command) {
2564
2550
  const c = String(command || "").trim().toLowerCase();
@@ -3291,6 +3277,12 @@ var MAX_SCRATCHPAD_FILES = 1e3;
3291
3277
  var MAX_SCRATCHPAD_BYTES = 10 * 1024 * 1024;
3292
3278
  var MAX_SESSION_SCRATCHPAD_FILES = 200;
3293
3279
  var MAX_SESSION_SCRATCHPAD_BYTES = 2 * 1024 * 1024;
3280
+ var CORRUPTION_BACKUP_MAX = 5;
3281
+ var CORRUPTION_BACKUP_TTL_MS = 24 * 60 * 60 * 1e3;
3282
+ var LEDGER_ROTATE_MAX_BYTES = 256 * 1024;
3283
+ var LEDGER_ROTATE_MAX_LINES = 1e4;
3284
+ var LEDGER_ROTATE_MAX_AGE_MS = 48 * 60 * 60 * 1e3;
3285
+ var ACTIVE_JOBS_STALE_MS = 72 * 60 * 60 * 1e3;
3294
3286
  var MAX_PTR_CANDIDATES = 50;
3295
3287
  var SUMMARY_HEAD_TRUNCATE = 500;
3296
3288
  function getVibeOSHome3() {
@@ -3421,19 +3413,61 @@ var tool = Object.assign((def) => def, {
3421
3413
  enum: (values) => _zType({ kind: "enum", values })
3422
3414
  }
3423
3415
  });
3424
- function _handleStateCorruption2(path) {
3416
+ function _pruneCorruptionBackups(backupDir) {
3417
+ try {
3418
+ if (!existsSync5(backupDir))
3419
+ return;
3420
+ const now = Date.now();
3421
+ const backups = readdirSync(backupDir).map((name) => {
3422
+ const path = join4(backupDir, name);
3423
+ try {
3424
+ const st = statSync4(path);
3425
+ return { name, path, mtimeMs: st.mtimeMs };
3426
+ } catch {
3427
+ return null;
3428
+ }
3429
+ }).filter((entry) => !!entry && entry.name.includes(".corrupted.")).sort((a, b) => b.mtimeMs - a.mtimeMs);
3430
+ const keep = new Set(backups.slice(0, CORRUPTION_BACKUP_MAX).map((b) => b.path));
3431
+ for (const backup of backups) {
3432
+ const isExpired = now - backup.mtimeMs > CORRUPTION_BACKUP_TTL_MS;
3433
+ if (isExpired || !keep.has(backup.path)) {
3434
+ try {
3435
+ rmSync2(backup.path, { force: true });
3436
+ } catch {
3437
+ }
3438
+ }
3439
+ }
3440
+ } catch {
3441
+ }
3442
+ }
3443
+ var _startupMaintenanceHome = "";
3444
+ function runStartupMaintenanceOnce() {
3445
+ try {
3446
+ const home = getVibeOSHome3();
3447
+ if (!home || home === _startupMaintenanceHome)
3448
+ return;
3449
+ _startupMaintenanceHome = home;
3450
+ _pruneCorruptionBackups(join4(home, ".backups"));
3451
+ loadActiveJobs();
3452
+ _compactSavingsLedgerIfNeeded();
3453
+ } catch {
3454
+ }
3455
+ }
3456
+ function _handleStateCorruption(path) {
3425
3457
  const backupDir = join4(VIBEOS_HOME, ".backups");
3426
- mkdirSync4(backupDir, { recursive: true });
3427
- const backupPath = join4(backupDir, basename3(path) + ".corrupted." + Date.now());
3458
+ mkdirSync3(backupDir, { recursive: true });
3459
+ const backupPath = join4(backupDir, basename2(path) + ".corrupted." + Date.now());
3428
3460
  try {
3429
- copyFileSync2(path, backupPath);
3461
+ copyFileSync(path, backupPath);
3430
3462
  } catch {
3431
3463
  }
3432
3464
  const logPath = join4(VIBEOS_HOME, ".state-corruption-log.jsonl");
3433
3465
  try {
3434
- appendFileSync3(logPath, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), path, backup: backupPath }) + "\n");
3466
+ appendFileSync2(logPath, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), path, backup: backupPath }) + "\n");
3435
3467
  } catch {
3436
3468
  }
3469
+ _pruneCorruptionBackups(backupDir);
3470
+ return backupPath;
3437
3471
  }
3438
3472
  function _lockPathFor(filePath) {
3439
3473
  const hash = createHash("sha1").update(String(filePath || "")).digest("hex");
@@ -3446,7 +3480,7 @@ function withFileLock(filePath, fn, opts = {}) {
3446
3480
  const start = Date.now();
3447
3481
  while (Date.now() - start < timeoutMs) {
3448
3482
  try {
3449
- mkdirSync4(FILE_LOCK_DIR, { recursive: true });
3483
+ mkdirSync3(FILE_LOCK_DIR, { recursive: true });
3450
3484
  const fd = openSync(lockPath, "wx");
3451
3485
  try {
3452
3486
  writeFileSync4(fd, `${process.pid}
@@ -3524,12 +3558,12 @@ function readJsonOrEmpty(filePath) {
3524
3558
  return {};
3525
3559
  const st = statSync4(filePath);
3526
3560
  if (st.size > 10485760) {
3527
- _handleStateCorruption2(filePath);
3561
+ _handleStateCorruption(filePath);
3528
3562
  return {};
3529
3563
  }
3530
3564
  return safeJsonParse3(readFileSync4(filePath, "utf-8"));
3531
3565
  } catch {
3532
- _handleStateCorruption2(filePath);
3566
+ _handleStateCorruption(filePath);
3533
3567
  return {};
3534
3568
  }
3535
3569
  }
@@ -3554,7 +3588,7 @@ function updateState(mutator) {
3554
3588
  state._gen = preGen + 1;
3555
3589
  const next = mutator(state) ?? state;
3556
3590
  validateState(next, delegationStateFile);
3557
- mkdirSync4(dirname4(delegationStateFile), { recursive: true });
3591
+ mkdirSync3(dirname4(delegationStateFile), { recursive: true });
3558
3592
  const tmp = delegationStateFile + ".tmp";
3559
3593
  writeFileSync4(tmp, JSON.stringify(next, null, 2) + "\n");
3560
3594
  renameSync3(tmp, delegationStateFile);
@@ -3580,12 +3614,12 @@ function readFullState() {
3580
3614
  return {};
3581
3615
  const st = statSync4(delegationStateFile);
3582
3616
  if (st.size > 10485760) {
3583
- _handleStateCorruption2(delegationStateFile);
3617
+ _handleStateCorruption(delegationStateFile);
3584
3618
  return {};
3585
3619
  }
3586
3620
  return safeJsonParse3(readFileSync4(delegationStateFile, "utf-8"));
3587
3621
  } catch {
3588
- _handleStateCorruption2(delegationStateFile);
3622
+ _handleStateCorruption(delegationStateFile);
3589
3623
  return {};
3590
3624
  }
3591
3625
  }
@@ -3625,7 +3659,7 @@ function loadGlobalLearning() {
3625
3659
  return DFLT_GL;
3626
3660
  const st = statSync4(globalLearningFile);
3627
3661
  if (st.size > 10485760) {
3628
- _handleStateCorruption2(globalLearningFile);
3662
+ _handleStateCorruption(globalLearningFile);
3629
3663
  return DFLT_GL;
3630
3664
  }
3631
3665
  const j = safeJsonParse3(readFileSync4(globalLearningFile, "utf-8"));
@@ -3638,7 +3672,7 @@ function loadGlobalLearning() {
3638
3672
  j.context7_last_seen ??= null;
3639
3673
  return j;
3640
3674
  } catch {
3641
- _handleStateCorruption2(globalLearningFile);
3675
+ _handleStateCorruption(globalLearningFile);
3642
3676
  return DFLT_GL;
3643
3677
  }
3644
3678
  }
@@ -3648,7 +3682,7 @@ function updateGlobalLearning(mutator) {
3648
3682
  const s = loadGlobalLearning();
3649
3683
  const next = mutator(s) ?? s;
3650
3684
  next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3651
- mkdirSync4(dirname4(globalLearningFile), { recursive: true });
3685
+ mkdirSync3(dirname4(globalLearningFile), { recursive: true });
3652
3686
  const tmp = globalLearningFile + ".tmp";
3653
3687
  writeFileSync4(tmp, JSON.stringify(next, null, 2));
3654
3688
  renameSync3(tmp, globalLearningFile);
@@ -3700,21 +3734,79 @@ function loadBlackboxState() {
3700
3734
  return { enabled: true, sessions: {} };
3701
3735
  const st = statSync4(blackboxFile);
3702
3736
  if (st.size > 10485760) {
3703
- _handleStateCorruption2(blackboxFile);
3737
+ _handleStateCorruption(blackboxFile);
3704
3738
  return { enabled: false, sessions: {} };
3705
3739
  }
3706
- return safeJsonParse3(readFileSync4(blackboxFile, "utf-8")) || { enabled: false, sessions: {} };
3740
+ const raw = safeJsonParse3(readFileSync4(blackboxFile, "utf-8")) || { enabled: false, sessions: {} };
3741
+ if (!raw.sessions || typeof raw.sessions !== "object")
3742
+ raw.sessions = {};
3743
+ const now = Date.now();
3744
+ let changed = false;
3745
+ for (const [sid, session] of Object.entries(raw.sessions)) {
3746
+ if (!session || typeof session !== "object")
3747
+ continue;
3748
+ const next = { ...session };
3749
+ const createdAtRaw = typeof next.createdAt === "string" ? next.createdAt : "";
3750
+ const updatedAtRaw = typeof next.updatedAt === "string" ? next.updatedAt : "";
3751
+ const startedRaw = typeof next.started === "string" ? next.started : "";
3752
+ const sessionStartedRaw = typeof next.session_started_at === "string" ? next.session_started_at : "";
3753
+ const anchorRaw = [createdAtRaw, updatedAtRaw, startedRaw, sessionStartedRaw].find((v) => v && !Number.isNaN(Date.parse(v)));
3754
+ const anchorMs = anchorRaw ? Date.parse(anchorRaw) : NaN;
3755
+ if (!Number.isFinite(Date.parse(createdAtRaw))) {
3756
+ next.createdAt = Number.isFinite(anchorMs) ? new Date(anchorMs).toISOString() : new Date(now).toISOString();
3757
+ changed = true;
3758
+ }
3759
+ if (!Number.isFinite(Date.parse(updatedAtRaw))) {
3760
+ next.updatedAt = next.createdAt || new Date(now).toISOString();
3761
+ changed = true;
3762
+ }
3763
+ if (typeof next.sessionId !== "string" || !next.sessionId.trim()) {
3764
+ next.sessionId = String(sid || "");
3765
+ changed = true;
3766
+ }
3767
+ raw.sessions[sid] = next;
3768
+ }
3769
+ if (changed) {
3770
+ try {
3771
+ saveBlackboxState(raw);
3772
+ } catch {
3773
+ }
3774
+ }
3775
+ return raw;
3707
3776
  } catch {
3708
- _handleStateCorruption2(blackboxFile);
3777
+ _handleStateCorruption(blackboxFile);
3709
3778
  return { enabled: false, sessions: {} };
3710
3779
  }
3711
3780
  }
3712
3781
  function saveBlackboxState(state) {
3713
3782
  const blackboxFile = join4(getVibeOSHome3(), "blackbox-state.json");
3714
3783
  try {
3715
- mkdirSync4(dirname4(blackboxFile), { recursive: true });
3784
+ const next = state && typeof state === "object" ? state : { enabled: true, sessions: {} };
3785
+ next.sessions ??= {};
3786
+ const now = Date.now();
3787
+ for (const [sid, session] of Object.entries(next.sessions)) {
3788
+ if (!session || typeof session !== "object")
3789
+ continue;
3790
+ const record = session;
3791
+ const createdAtRaw = typeof record.createdAt === "string" ? record.createdAt : "";
3792
+ const updatedAtRaw = typeof record.updatedAt === "string" ? record.updatedAt : "";
3793
+ const startedRaw = typeof record.started === "string" ? record.started : "";
3794
+ const sessionStartedRaw = typeof record.session_started_at === "string" ? record.session_started_at : "";
3795
+ const anchorRaw = [createdAtRaw, updatedAtRaw, startedRaw, sessionStartedRaw].find((v) => v && !Number.isNaN(Date.parse(v)));
3796
+ const anchorMs = anchorRaw ? Date.parse(anchorRaw) : NaN;
3797
+ if (!Number.isFinite(Date.parse(createdAtRaw))) {
3798
+ record.createdAt = Number.isFinite(anchorMs) ? new Date(anchorMs).toISOString() : new Date(now).toISOString();
3799
+ }
3800
+ if (!Number.isFinite(Date.parse(updatedAtRaw))) {
3801
+ record.updatedAt = record.createdAt || new Date(now).toISOString();
3802
+ }
3803
+ if (typeof record.sessionId !== "string" || !record.sessionId.trim()) {
3804
+ record.sessionId = String(sid || "");
3805
+ }
3806
+ }
3807
+ mkdirSync3(dirname4(blackboxFile), { recursive: true });
3716
3808
  const tmp = blackboxFile + ".tmp";
3717
- writeFileSync4(tmp, JSON.stringify(state, null, 2) + "\n");
3809
+ writeFileSync4(tmp, JSON.stringify(next, null, 2) + "\n");
3718
3810
  renameSync3(tmp, blackboxFile);
3719
3811
  } catch (err) {
3720
3812
  console.error(`[vibeOS] saveBlackboxState failed: ${err.message}`);
@@ -3734,7 +3826,7 @@ function getGlobalIndexPath() {
3734
3826
  }
3735
3827
  function ensureSessionScratchpadDirs() {
3736
3828
  try {
3737
- mkdirSync4(getSessionScratchpadDir(), { recursive: true });
3829
+ mkdirSync3(getSessionScratchpadDir(), { recursive: true });
3738
3830
  return true;
3739
3831
  } catch {
3740
3832
  return false;
@@ -3778,7 +3870,8 @@ function _flushLedgerBuffer() {
3778
3870
  const lines = batch.map((e) => typeof e === "string" ? e.trimEnd() : String(e).trimEnd());
3779
3871
  const joined = lines.filter(Boolean).map((l) => l + "\n").join("");
3780
3872
  try {
3781
- appendFileSync3(SAVINGS_LEDGER_FILE, joined);
3873
+ appendFileSync2(SAVINGS_LEDGER_FILE, joined);
3874
+ _compactSavingsLedgerIfNeeded();
3782
3875
  } catch {
3783
3876
  }
3784
3877
  }
@@ -3925,10 +4018,10 @@ function indexAppend(hash, tool2, size, extra) {
3925
4018
  const entry = JSON.stringify(entryObj) + "\n";
3926
4019
  const globalIndex = getGlobalIndexPath();
3927
4020
  const sessionIndex = getSessionIndexPath();
3928
- mkdirSync4(dirname4(globalIndex), { recursive: true });
3929
- mkdirSync4(dirname4(sessionIndex), { recursive: true });
3930
- appendFileSync3(globalIndex, entry);
3931
- appendFileSync3(sessionIndex, entry);
4021
+ mkdirSync3(dirname4(globalIndex), { recursive: true });
4022
+ mkdirSync3(dirname4(sessionIndex), { recursive: true });
4023
+ appendFileSync2(globalIndex, entry);
4024
+ appendFileSync2(sessionIndex, entry);
3932
4025
  } catch (err) {
3933
4026
  console.error(`[vibeOS] index write failed: ${err.message}`);
3934
4027
  }
@@ -4181,21 +4274,83 @@ function pruneScratchpadOnce() {
4181
4274
  }
4182
4275
  cleanupStaleSessionScratchpads();
4183
4276
  }
4184
- function loadActiveJobs() {
4277
+ function _readActiveJobsRaw() {
4185
4278
  try {
4186
4279
  if (!existsSync5(ACTIVE_JOBS_FILE))
4187
4280
  return {};
4188
- const st = statSync4(ACTIVE_JOBS_FILE);
4189
- if (st.size > 10485760) {
4190
- _handleStateCorruption2(ACTIVE_JOBS_FILE);
4191
- return {};
4192
- }
4193
4281
  const raw = safeJsonParse3(readFileSync4(ACTIVE_JOBS_FILE, "utf-8"));
4194
- if (!raw || typeof raw !== "object")
4195
- return {};
4196
- return raw;
4282
+ return raw && typeof raw === "object" ? raw : {};
4283
+ } catch {
4284
+ _handleStateCorruption(ACTIVE_JOBS_FILE);
4285
+ return {};
4286
+ }
4287
+ }
4288
+ function _writeActiveJobsRaw(jobs) {
4289
+ try {
4290
+ mkdirSync3(dirname4(ACTIVE_JOBS_FILE), { recursive: true });
4291
+ const tmp = ACTIVE_JOBS_FILE + ".tmp";
4292
+ writeFileSync4(tmp, JSON.stringify(jobs, null, 2) + "\n");
4293
+ renameSync3(tmp, ACTIVE_JOBS_FILE);
4294
+ } catch {
4295
+ }
4296
+ }
4297
+ function _normalizeActiveJobRecord(record, now = Date.now(), strict = false) {
4298
+ if (!record || typeof record !== "object")
4299
+ return { record: null, changed: false, stale: false };
4300
+ const next = { ...record };
4301
+ let changed = false;
4302
+ const updatedAtRaw = typeof next.updatedAt === "string" ? next.updatedAt : "";
4303
+ const createdAtRaw = typeof next.createdAt === "string" ? next.createdAt : "";
4304
+ const updatedAtMs = Date.parse(updatedAtRaw);
4305
+ const createdAtMs = Date.parse(createdAtRaw);
4306
+ const anchorMs = Number.isFinite(updatedAtMs) ? updatedAtMs : createdAtMs;
4307
+ const stale = Number.isFinite(anchorMs) && now - anchorMs > ACTIVE_JOBS_STALE_MS;
4308
+ if (strict && (!next.status || typeof next.status !== "string" || !next.status.trim()))
4309
+ return { record: null, changed: false, stale };
4310
+ if (strict && !Number.isFinite(createdAtMs))
4311
+ return { record: null, changed: false, stale };
4312
+ if (!Number.isFinite(createdAtMs)) {
4313
+ next.createdAt = Number.isFinite(anchorMs) ? new Date(anchorMs).toISOString() : new Date(now).toISOString();
4314
+ changed = true;
4315
+ }
4316
+ if (!Number.isFinite(updatedAtMs)) {
4317
+ next.updatedAt = next.createdAt || new Date(now).toISOString();
4318
+ changed = true;
4319
+ }
4320
+ if (typeof next.status !== "string" || !next.status.trim()) {
4321
+ next.status = "active";
4322
+ changed = true;
4323
+ }
4324
+ if (stale && next.status !== "completed") {
4325
+ next.status = "completed";
4326
+ next.completedAt = new Date(now).toISOString();
4327
+ changed = true;
4328
+ }
4329
+ return { record: next, changed, stale };
4330
+ }
4331
+ function loadActiveJobs() {
4332
+ try {
4333
+ return withFileLock(ACTIVE_JOBS_FILE, () => {
4334
+ const raw = _readActiveJobsRaw();
4335
+ const next = {};
4336
+ let changed = false;
4337
+ const now = Date.now();
4338
+ for (const [key, value] of Object.entries(raw || {})) {
4339
+ const norm = _normalizeActiveJobRecord(value, now, true);
4340
+ if (!norm.record) {
4341
+ changed = true;
4342
+ continue;
4343
+ }
4344
+ next[key] = norm.record;
4345
+ if (norm.changed)
4346
+ changed = true;
4347
+ }
4348
+ if (changed)
4349
+ _writeActiveJobsRaw(next);
4350
+ return next;
4351
+ });
4197
4352
  } catch {
4198
- _handleStateCorruption2(ACTIVE_JOBS_FILE);
4353
+ _handleStateCorruption(ACTIVE_JOBS_FILE);
4199
4354
  return {};
4200
4355
  }
4201
4356
  }
@@ -4212,15 +4367,19 @@ function saveActiveJobForProject(job, fp2 = currentProjectFingerprint) {
4212
4367
  if (!fp2 || !job || typeof job !== "object")
4213
4368
  return;
4214
4369
  try {
4215
- const jobs = loadActiveJobs();
4216
- jobs[fp2] = job;
4217
- mkdirSync4(dirname4(ACTIVE_JOBS_FILE), { recursive: true });
4218
- const tmp = ACTIVE_JOBS_FILE + ".tmp";
4219
- writeFileSync4(tmp, JSON.stringify(jobs, null, 2));
4220
- renameSync3(tmp, ACTIVE_JOBS_FILE);
4370
+ withFileLock(ACTIVE_JOBS_FILE, () => {
4371
+ const jobs = _readActiveJobsRaw();
4372
+ const norm = _normalizeActiveJobRecord(job);
4373
+ jobs[fp2] = norm.record || job;
4374
+ _writeActiveJobsRaw(jobs);
4375
+ });
4221
4376
  } catch {
4222
4377
  }
4223
4378
  }
4379
+ try {
4380
+ loadActiveJobs();
4381
+ } catch {
4382
+ }
4224
4383
  function projectFingerprint(dir) {
4225
4384
  if (!dir)
4226
4385
  return "unknown";
@@ -4242,7 +4401,7 @@ function saveProjectState(state) {
4242
4401
  const projectStateFile = join4(getVibeOSHome3(), "project-states.json");
4243
4402
  try {
4244
4403
  withFileLock(projectStateFile, () => {
4245
- mkdirSync4(dirname4(projectStateFile), { recursive: true });
4404
+ mkdirSync3(dirname4(projectStateFile), { recursive: true });
4246
4405
  const _tmp = projectStateFile + ".tmp." + Date.now();
4247
4406
  writeFileSync4(_tmp, JSON.stringify(state, null, 2) + "\n", "utf-8");
4248
4407
  renameSync3(_tmp, projectStateFile);
@@ -4259,11 +4418,49 @@ function ensureProjectBucket(state, fp2) {
4259
4418
  researchChains: 0,
4260
4419
  context7Bypasses: 0,
4261
4420
  commonTopics: [],
4421
+ sessions: [],
4422
+ reports: [],
4423
+ updatedAt: null,
4424
+ lastSeen: null,
4262
4425
  techStack: detectTechStack(process.cwd())
4263
4426
  };
4264
4427
  }
4265
4428
  return state.project_hashes[fp2];
4266
4429
  }
4430
+ function touchProjectBucket(state, fp2, meta = {}) {
4431
+ if (!fp2 || fp2 === "unknown")
4432
+ return null;
4433
+ const bucket = ensureProjectBucket(state, fp2);
4434
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4435
+ bucket.updatedAt = now;
4436
+ bucket.lastSeen = now;
4437
+ if (typeof meta.projectName === "string" && meta.projectName.trim()) {
4438
+ bucket.projectName = meta.projectName.trim();
4439
+ }
4440
+ if (typeof meta.sessionId === "string" && meta.sessionId.trim()) {
4441
+ bucket.sessions ??= [];
4442
+ if (!bucket.sessions.includes(meta.sessionId)) {
4443
+ bucket.sessions.push(meta.sessionId);
4444
+ bucket.sessions = bucket.sessions.slice(-30);
4445
+ }
4446
+ bucket.totalSessions = Math.max(Number(bucket.totalSessions || 0), bucket.sessions.length);
4447
+ }
4448
+ if (typeof meta.reportId === "string" && meta.reportId.trim()) {
4449
+ bucket.reports ??= [];
4450
+ if (!bucket.reports.includes(meta.reportId)) {
4451
+ bucket.reports.push(meta.reportId);
4452
+ bucket.reports = bucket.reports.slice(-50);
4453
+ }
4454
+ }
4455
+ if (typeof meta.topic === "string" && meta.topic.trim()) {
4456
+ bucket.commonTopics ??= [];
4457
+ if (!bucket.commonTopics.includes(meta.topic)) {
4458
+ bucket.commonTopics.push(meta.topic);
4459
+ bucket.commonTopics = bucket.commonTopics.slice(-20);
4460
+ }
4461
+ }
4462
+ return bucket;
4463
+ }
4267
4464
  function detectTechStack(dir) {
4268
4465
  const stacks = [];
4269
4466
  try {
@@ -4394,6 +4591,18 @@ function recordCacheSaving(tool2, saveEst, meta = {}) {
4394
4591
  s.sessions[sid2].cache_savings_usd = roundUsd(Number(s.sessions[sid2].cache_savings_usd || 0) + delta);
4395
4592
  s.lifetime.cache_savings_usd = roundUsd(Number(s.lifetime.cache_savings_usd || 0) + delta);
4396
4593
  }
4594
+ try {
4595
+ if (currentProjectFingerprint) {
4596
+ const pstate = loadProjectState();
4597
+ touchProjectBucket(pstate, currentProjectFingerprint, {
4598
+ sessionId: sid2,
4599
+ projectName: currentProjectName || "",
4600
+ topic: meta?.hash ? String(meta.hash).slice(0, 16) : "cache"
4601
+ });
4602
+ saveProjectState(pstate);
4603
+ }
4604
+ } catch {
4605
+ }
4397
4606
  _pruneOldSessions(s);
4398
4607
  return s;
4399
4608
  });
@@ -4424,6 +4633,20 @@ function recordMissedContext7(saveEst) {
4424
4633
  const sid = _OC_SID;
4425
4634
  s.sessions[sid] ??= { total_savings_usd: 0, cache_savings_usd: 0, project_name: "", warns: [], cache_hits: [], seenWarnKeys: {} };
4426
4635
  s.sessions[sid].context7_missed_usd = Math.round(((s.sessions[sid].context7_missed_usd || 0) + saveEst) * 100) / 100;
4636
+ try {
4637
+ if (currentProjectFingerprint) {
4638
+ const pstate = loadProjectState();
4639
+ const bucket = touchProjectBucket(pstate, currentProjectFingerprint, {
4640
+ sessionId: sid,
4641
+ projectName: currentProjectName || "",
4642
+ topic: "context7"
4643
+ });
4644
+ if (bucket)
4645
+ bucket.context7Bypasses = (bucket.context7Bypasses || 0) + 1;
4646
+ saveProjectState(pstate);
4647
+ }
4648
+ } catch {
4649
+ }
4427
4650
  return s;
4428
4651
  });
4429
4652
  try {
@@ -4442,16 +4665,6 @@ function recordMissedContext7(saveEst) {
4442
4665
  _ledgerBufferTimer = setTimeout(_flushLedgerBuffer, LEDGER_BUFFER_FLUSH_MS);
4443
4666
  } catch {
4444
4667
  }
4445
- try {
4446
- if (currentProjectFingerprint) {
4447
- const pstate = loadProjectState();
4448
- const bucket = ensureProjectBucket(pstate, currentProjectFingerprint);
4449
- bucket.context7Bypasses = (bucket.context7Bypasses || 0) + 1;
4450
- bucket.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
4451
- saveProjectState(pstate);
4452
- }
4453
- } catch {
4454
- }
4455
4668
  try {
4456
4669
  updateGlobalLearning((gl) => {
4457
4670
  gl.context7_bypasses = Number(gl.context7_bypasses || 0) + 1;
@@ -4479,7 +4692,7 @@ function loadTodos() {
4479
4692
  }
4480
4693
  function saveTodos(todos) {
4481
4694
  try {
4482
- mkdirSync4(dirname4(TODOS_FILE), { recursive: true });
4695
+ mkdirSync3(dirname4(TODOS_FILE), { recursive: true });
4483
4696
  const tmp = TODOS_FILE + ".tmp." + Date.now();
4484
4697
  writeFileSync4(tmp, JSON.stringify(todos, null, 2), "utf-8");
4485
4698
  renameSync3(tmp, TODOS_FILE);
@@ -4518,6 +4731,57 @@ function markTodoDone(id2) {
4518
4731
  function getTodos() {
4519
4732
  return loadTodos();
4520
4733
  }
4734
+ function _compactSavingsLedgerIfNeeded() {
4735
+ try {
4736
+ if (!existsSync5(SAVINGS_LEDGER_FILE))
4737
+ return;
4738
+ const st = statSync4(SAVINGS_LEDGER_FILE);
4739
+ if (st.size <= LEDGER_ROTATE_MAX_BYTES)
4740
+ return;
4741
+ withFileLock(SAVINGS_LEDGER_FILE, () => {
4742
+ if (!existsSync5(SAVINGS_LEDGER_FILE))
4743
+ return;
4744
+ const lockedStat = statSync4(SAVINGS_LEDGER_FILE);
4745
+ if (lockedStat.size <= LEDGER_ROTATE_MAX_BYTES)
4746
+ return;
4747
+ const raw = readFileSync4(SAVINGS_LEDGER_FILE, "utf-8");
4748
+ if (!raw.trim())
4749
+ return;
4750
+ const now = Date.now();
4751
+ const rows = raw.split("\n").filter(Boolean).map((line) => {
4752
+ let rec = null;
4753
+ try {
4754
+ rec = JSON.parse(line);
4755
+ } catch {
4756
+ rec = null;
4757
+ }
4758
+ const atRaw = rec && typeof rec === "object" ? String(rec.at || rec.ts || "") : "";
4759
+ const atMs = Date.parse(atRaw);
4760
+ return { raw: line.trim(), atMs: Number.isFinite(atMs) ? atMs : null };
4761
+ }).filter((row) => row.raw);
4762
+ const recent = rows.filter((row) => row.atMs != null && now - Number(row.atMs) <= LEDGER_ROTATE_MAX_AGE_MS);
4763
+ const pool = recent.length > 0 ? recent : rows;
4764
+ const capped = pool.length > LEDGER_ROTATE_MAX_LINES ? pool.slice(-LEDGER_ROTATE_MAX_LINES) : pool;
4765
+ let size = 0;
4766
+ const kept = [];
4767
+ for (let i = capped.length - 1; i >= 0; i--) {
4768
+ const line = capped[i].raw;
4769
+ const lineBytes = Buffer.byteLength(line + "\n", "utf-8");
4770
+ if (kept.length > 0 && size + lineBytes > LEDGER_ROTATE_MAX_BYTES)
4771
+ break;
4772
+ kept.push(line);
4773
+ size += lineBytes;
4774
+ }
4775
+ const compacted = kept.reverse().join("\n") + "\n";
4776
+ if (compacted.trim() && compacted !== raw) {
4777
+ const tmp = SAVINGS_LEDGER_FILE + ".tmp." + Date.now();
4778
+ writeFileSync4(tmp, compacted, "utf-8");
4779
+ renameSync3(tmp, SAVINGS_LEDGER_FILE);
4780
+ }
4781
+ }, { timeoutMs: 4e3 });
4782
+ } catch {
4783
+ }
4784
+ }
4521
4785
  function readLedgerTotals() {
4522
4786
  const empty = { delegation: 0, cache: 0, context7: 0, total: 0, entries: 0 };
4523
4787
  try {
@@ -4526,15 +4790,19 @@ function readLedgerTotals() {
4526
4790
  return empty;
4527
4791
  }
4528
4792
  const st = statSync4(SAVINGS_LEDGER_FILE);
4529
- if (st.size > 10485760) {
4530
- _handleStateCorruption2(SAVINGS_LEDGER_FILE);
4531
- return empty;
4532
- }
4533
4793
  if (st.size === 0) {
4534
4794
  _ledgerTotalsCache = { mtime: st.mtimeMs, size: 0, delegation: 0, cache: 0, context7: 0, entries: 0 };
4535
4795
  return empty;
4536
4796
  }
4537
- if (_ledgerTotalsCache.mtime === st.mtimeMs && _ledgerTotalsCache.size === st.size) {
4797
+ if (st.size > LEDGER_ROTATE_MAX_BYTES) {
4798
+ _compactSavingsLedgerIfNeeded();
4799
+ }
4800
+ const currentStat = statSync4(SAVINGS_LEDGER_FILE);
4801
+ if (currentStat.size === 0) {
4802
+ _ledgerTotalsCache = { mtime: currentStat.mtimeMs, size: 0, delegation: 0, cache: 0, context7: 0, entries: 0 };
4803
+ return empty;
4804
+ }
4805
+ if (_ledgerTotalsCache.mtime === currentStat.mtimeMs && _ledgerTotalsCache.size === currentStat.size) {
4538
4806
  return {
4539
4807
  delegation: Math.round(_ledgerTotalsCache.delegation * 1e3) / 1e3,
4540
4808
  cache: Math.round(_ledgerTotalsCache.cache * 1e3) / 1e3,
@@ -4548,9 +4816,9 @@ function readLedgerTotals() {
4548
4816
  let context7 = 0;
4549
4817
  let entries = 0;
4550
4818
  let raw = "";
4551
- let incremental = _ledgerTotalsCache.size > 0 && st.size >= _ledgerTotalsCache.size && _ledgerTotalsCache.mtime > 0;
4819
+ let incremental = _ledgerTotalsCache.size > 0 && currentStat.size >= _ledgerTotalsCache.size && _ledgerTotalsCache.mtime > 0;
4552
4820
  if (incremental) {
4553
- const deltaSize = st.size - _ledgerTotalsCache.size;
4821
+ const deltaSize = currentStat.size - _ledgerTotalsCache.size;
4554
4822
  if (deltaSize > 0) {
4555
4823
  const fd = openSync(SAVINGS_LEDGER_FILE, "r");
4556
4824
  try {
@@ -4576,8 +4844,8 @@ function readLedgerTotals() {
4576
4844
  }
4577
4845
  if (!raw.trim()) {
4578
4846
  _ledgerTotalsCache = {
4579
- mtime: st.mtimeMs,
4580
- size: st.size,
4847
+ mtime: currentStat.mtimeMs,
4848
+ size: currentStat.size,
4581
4849
  delegation,
4582
4850
  cache,
4583
4851
  context7,
@@ -4620,7 +4888,7 @@ function readLedgerTotals() {
4620
4888
  else
4621
4889
  delegation += amt;
4622
4890
  }
4623
- _ledgerTotalsCache = { mtime: st.mtimeMs, size: st.size, delegation, cache, context7, entries };
4891
+ _ledgerTotalsCache = { mtime: currentStat.mtimeMs, size: currentStat.size, delegation, cache, context7, entries };
4624
4892
  const total = delegation + cache;
4625
4893
  return {
4626
4894
  delegation: Math.round(delegation * 1e3) / 1e3,
@@ -4705,7 +4973,7 @@ function saveSessionCheckpoint() {
4705
4973
  model: session.model || ""
4706
4974
  };
4707
4975
  const cpPath = join4(getSessionRoot(), "checkpoint.json");
4708
- mkdirSync4(dirname4(cpPath), { recursive: true });
4976
+ mkdirSync3(dirname4(cpPath), { recursive: true });
4709
4977
  const tmp = cpPath + ".tmp";
4710
4978
  writeFileSync4(tmp, JSON.stringify(cp, null, 2) + "\n");
4711
4979
  renameSync3(tmp, cpPath);
@@ -4746,20 +5014,6 @@ function getOpenCodeDesktopHome() {
4746
5014
  return process.env.VIBEOS_OPENCODE_DESKTOP_HOME || join5(process.env.HOME || homedir4(), "Library", "Application Support", "ai.opencode.desktop");
4747
5015
  }
4748
5016
  var TIERS_FILE2 = join5(getVibeOSHome4(), "model-tiers.json");
4749
- function _handleStateCorruption3(path) {
4750
- const backupDir = join5(getVibeOSHome4(), ".backups");
4751
- mkdirSync5(backupDir, { recursive: true });
4752
- const backupPath = join5(backupDir, basename4(path) + ".corrupted." + Date.now());
4753
- try {
4754
- copyFileSync3(path, backupPath);
4755
- } catch {
4756
- }
4757
- const logPath = join5(getVibeOSHome4(), ".state-corruption-log.jsonl");
4758
- try {
4759
- appendFileSync4(logPath, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), path, backup: backupPath }) + "\n");
4760
- } catch {
4761
- }
4762
- }
4763
5017
  function _lockPathFor2(filePath) {
4764
5018
  const hash = createHash2("sha1").update(String(filePath || "")).digest("hex");
4765
5019
  return join5(getVibeOSHome4(), ".vibeOS-locks", `${hash}.lock`);
@@ -4771,7 +5025,7 @@ function withFileLock2(filePath, fn, opts = {}) {
4771
5025
  const start = Date.now();
4772
5026
  while (Date.now() - start < timeoutMs) {
4773
5027
  try {
4774
- mkdirSync5(join5(getVibeOSHome4(), ".vibeOS-locks"), { recursive: true });
5028
+ mkdirSync4(join5(getVibeOSHome4(), ".vibeOS-locks"), { recursive: true });
4775
5029
  const fd = openSync2(lockPath, "wx");
4776
5030
  try {
4777
5031
  writeFileSync5(fd, `${process.pid}
@@ -5289,7 +5543,7 @@ function _loadDynamicPricingCache() {
5289
5543
  return {};
5290
5544
  const st = statSync5(PRICING_CACHE_FILE2);
5291
5545
  if (st.size > 10485760) {
5292
- _handleStateCorruption3(PRICING_CACHE_FILE2);
5546
+ _handleStateCorruption(PRICING_CACHE_FILE2);
5293
5547
  _dynamicPricingCache = {};
5294
5548
  return {};
5295
5549
  }
@@ -5297,7 +5551,7 @@ function _loadDynamicPricingCache() {
5297
5551
  const map = raw?.models && typeof raw.models === "object" ? raw.models : {};
5298
5552
  _dynamicPricingCache = map;
5299
5553
  } catch {
5300
- _handleStateCorruption3(PRICING_CACHE_FILE2);
5554
+ _handleStateCorruption(PRICING_CACHE_FILE2);
5301
5555
  _dynamicPricingCache = {};
5302
5556
  }
5303
5557
  return _dynamicPricingCache;
@@ -5334,7 +5588,7 @@ function _writeDynamicPricingCache(modelsMap) {
5334
5588
  const PRICING_CACHE_FILE2 = join5(getVibeOSHome4(), "model-pricing-cache.json");
5335
5589
  try {
5336
5590
  withFileLock2(PRICING_CACHE_FILE2, () => {
5337
- mkdirSync5(dirname5(PRICING_CACHE_FILE2), { recursive: true });
5591
+ mkdirSync4(dirname5(PRICING_CACHE_FILE2), { recursive: true });
5338
5592
  let merged = {};
5339
5593
  try {
5340
5594
  if (existsSync6(PRICING_CACHE_FILE2)) {
@@ -5392,7 +5646,7 @@ function _loadPricingOverrides() {
5392
5646
  return {};
5393
5647
  const st = statSync5(tiersFile);
5394
5648
  if (st.size > 10485760) {
5395
- _handleStateCorruption3(tiersFile);
5649
+ _handleStateCorruption(tiersFile);
5396
5650
  _pricingOverridesCache = {};
5397
5651
  return {};
5398
5652
  }
@@ -5423,7 +5677,7 @@ function _loadPricingOverrides() {
5423
5677
  }
5424
5678
  _pricingOverridesCache = out;
5425
5679
  } catch {
5426
- _handleStateCorruption3(join5(home, "model-tiers.json"));
5680
+ _handleStateCorruption(join5(home, "model-tiers.json"));
5427
5681
  _pricingOverridesCache = {};
5428
5682
  }
5429
5683
  return _pricingOverridesCache;
@@ -5559,7 +5813,7 @@ function loadSelection2() {
5559
5813
  return DFLT_SEL2;
5560
5814
  const st = statSync5(TIERS_FILE3);
5561
5815
  if (st.size > 10485760) {
5562
- _handleStateCorruption3(TIERS_FILE3);
5816
+ _handleStateCorruption(TIERS_FILE3);
5563
5817
  return DFLT_SEL2;
5564
5818
  }
5565
5819
  const j = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8"));
@@ -5582,7 +5836,7 @@ function loadSelection2() {
5582
5836
  executed_model: j?.selection?.executed_model || null
5583
5837
  };
5584
5838
  } catch {
5585
- _handleStateCorruption3(TIERS_FILE3);
5839
+ _handleStateCorruption(TIERS_FILE3);
5586
5840
  return DFLT_SEL2;
5587
5841
  }
5588
5842
  }
@@ -5810,7 +6064,7 @@ function loadTrinitySlotsFromTiersFile() {
5810
6064
  return false;
5811
6065
  const st = statSync5(TIERS_FILE3);
5812
6066
  if (st.size > 10485760) {
5813
- _handleStateCorruption3(TIERS_FILE3);
6067
+ _handleStateCorruption(TIERS_FILE3);
5814
6068
  return false;
5815
6069
  }
5816
6070
  const tiersData = safeJsonParse3(readFileSync5(TIERS_FILE3, "utf-8")) || {};
@@ -5928,7 +6182,7 @@ function applySlot2(slot, projectDir = "") {
5928
6182
  }
5929
6183
 
5930
6184
  // src/lib/turn-classify.js
5931
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, existsSync as existsSync9, mkdirSync as mkdirSync8, renameSync as renameSync5 } from "node:fs";
6185
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, existsSync as existsSync9, mkdirSync as mkdirSync7, renameSync as renameSync5 } from "node:fs";
5932
6186
  import { join as join7, dirname as dirname8 } from "node:path";
5933
6187
 
5934
6188
  // src/vibeOS-lib/blackbox/resolution-tracker.js
@@ -7362,8 +7616,8 @@ function projectStructuredFromText(raw, selection, creditPercent = 0) {
7362
7616
  }
7363
7617
 
7364
7618
  // src/lib/reporting.js
7365
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync9, existsSync as existsSync11, mkdirSync as mkdirSync9, statSync as statSync6, copyFileSync as copyFileSync4, rmSync as rmSync4 } from "node:fs";
7366
- import { join as join9, basename as basename5 } from "node:path";
7619
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync9, existsSync as existsSync11, mkdirSync as mkdirSync8, statSync as statSync6, rmSync as rmSync4 } from "node:fs";
7620
+ import { join as join9 } from "node:path";
7367
7621
  function getVibeOSHome7() {
7368
7622
  return process.env.VIBEOS_HOME || join9(process.env.HOME || "", ".claude");
7369
7623
  }
@@ -7378,27 +7632,18 @@ var REPORTS_INDEX = getReportsIndexPath();
7378
7632
  var currentProjectFingerprint2 = "";
7379
7633
  var currentProjectName2 = "";
7380
7634
  var currentSessionId2 = "";
7381
- function _handleStateCorruption4(path) {
7382
- const backupDir = join9(getVibeOSHome7(), ".backups");
7383
- mkdirSync9(backupDir, { recursive: true });
7384
- const backupPath = join9(backupDir, basename5(path) + ".corrupted." + Date.now());
7385
- try {
7386
- copyFileSync4(path, backupPath);
7387
- } catch {
7388
- }
7389
- }
7390
7635
  function readJsonOrEmpty2(filePath) {
7391
7636
  try {
7392
7637
  if (!existsSync11(filePath))
7393
7638
  return {};
7394
7639
  const st = statSync6(filePath);
7395
7640
  if (st.size > 10485760) {
7396
- _handleStateCorruption4(filePath);
7641
+ _handleStateCorruption(filePath);
7397
7642
  return {};
7398
7643
  }
7399
7644
  return safeJsonParse3(readFileSync10(filePath, "utf-8"));
7400
7645
  } catch {
7401
- _handleStateCorruption4(filePath);
7646
+ _handleStateCorruption(filePath);
7402
7647
  return {};
7403
7648
  }
7404
7649
  }
@@ -7413,7 +7658,7 @@ function saveReportsIndex(idx) {
7413
7658
  const reportsIndexPath = getReportsIndexPath();
7414
7659
  const reportsDir = getReportsDir();
7415
7660
  withFileLock(reportsIndexPath, () => {
7416
- mkdirSync9(reportsDir, { recursive: true });
7661
+ mkdirSync8(reportsDir, { recursive: true });
7417
7662
  writeFileSync9(reportsIndexPath, JSON.stringify(idx, null, 2) + "\n");
7418
7663
  });
7419
7664
  } catch (err) {
@@ -7508,13 +7753,22 @@ function _parseMetrics(v) {
7508
7753
  function saveReport({ type = "manual", summary = "", findings = null, metrics = null, narrative = "", tags = [], fingerprint = null, status = "pending", task_description = "", outcome_verified = false } = {}) {
7509
7754
  const parsedFindings = _parseFindings(findings);
7510
7755
  const parsedMetrics = _parseMetrics(metrics);
7756
+ const metricsObject = parsedMetrics && typeof parsedMetrics === "object" && !Array.isArray(parsedMetrics) ? parsedMetrics : {};
7757
+ const metricsSessionId = typeof metricsObject.sessionId === "string" && metricsObject.sessionId.trim() ? metricsObject.sessionId.trim() : "";
7758
+ const metricsProjectName = typeof metricsObject.projectName === "string" && metricsObject.projectName.trim() ? metricsObject.projectName.trim() : "";
7759
+ const metricsProjectFingerprint = typeof metricsObject.projectFingerprint === "string" && metricsObject.projectFingerprint.trim() ? metricsObject.projectFingerprint.trim() : "";
7511
7760
  if (_wouldBeDuplicate(type, summary))
7512
7761
  return null;
7513
- const metricProjectFingerprint = typeof parsedMetrics?.projectFingerprint === "string" ? parsedMetrics.projectFingerprint : "";
7514
- const metricProjectName = typeof parsedMetrics?.projectName === "string" ? parsedMetrics.projectName : "";
7515
- const fp2 = fingerprint || currentProjectFingerprint2 || currentProjectFingerprint || metricProjectFingerprint || "unknown";
7516
- const projectName = currentProjectName2 || currentProjectName || metricProjectName || "unknown";
7517
- const sessionId = currentSessionId2 || getCurrentSessionId() || "unknown";
7762
+ if (!currentProjectFingerprint2 && metricsProjectFingerprint)
7763
+ currentProjectFingerprint2 = metricsProjectFingerprint;
7764
+ if (!currentProjectName2 && metricsProjectName)
7765
+ currentProjectName2 = metricsProjectName;
7766
+ if (!currentSessionId2 && metricsSessionId)
7767
+ currentSessionId2 = metricsSessionId;
7768
+ const liveSessionId = getCurrentSessionId() || getOcSessionId() || "";
7769
+ const fp2 = fingerprint || metricsProjectFingerprint || currentProjectFingerprint || currentProjectFingerprint2 || "unknown";
7770
+ const projectName = metricsProjectName || currentProjectName || currentProjectName2 || "unknown";
7771
+ const sessionId = metricsSessionId || liveSessionId || currentSessionId2 || "unknown";
7518
7772
  const id2 = generateReportId(type, fp2);
7519
7773
  const report = {
7520
7774
  meta: { id: id2, project: projectName, fingerprint: fp2, type, created: (/* @__PURE__ */ new Date()).toISOString(), sessionId },
@@ -7531,13 +7785,26 @@ function saveReport({ type = "manual", summary = "", findings = null, metrics =
7531
7785
  const reportsIndexPath = getReportsIndexPath();
7532
7786
  const reportsDir = getReportsDir();
7533
7787
  withFileLock(reportsIndexPath, () => {
7534
- mkdirSync9(reportsDir, { recursive: true });
7788
+ mkdirSync8(reportsDir, { recursive: true });
7535
7789
  writeFileSync9(join9(reportsDir, `${id2}.json`), JSON.stringify(report, null, 2) + "\n");
7536
7790
  const idx = reportsIndex();
7537
7791
  const _sum = (summary || "").slice(0, 80);
7538
7792
  idx.reports.push({ id: id2, type, project: report.meta.project, fingerprint: fp2, created: report.meta.created, summary: _sum });
7539
7793
  writeFileSync9(reportsIndexPath, JSON.stringify(idx, null, 2) + "\n");
7540
7794
  });
7795
+ try {
7796
+ if (fp2 && fp2 !== "unknown") {
7797
+ const pstate = loadProjectState();
7798
+ touchProjectBucket(pstate, fp2, {
7799
+ sessionId,
7800
+ projectName: projectName || "",
7801
+ reportId: id2,
7802
+ topic: type || "report"
7803
+ });
7804
+ saveProjectState(pstate);
7805
+ }
7806
+ } catch {
7807
+ }
7541
7808
  } catch (err) {
7542
7809
  console.error(`[vibeOS] report/index write failed: ${err.message}`);
7543
7810
  return null;
@@ -7894,6 +8161,14 @@ var RAW_MODE = {
7894
8161
  desc: "Pure v4 Pro baseline. No vibeOS overhead."
7895
8162
  };
7896
8163
  var ALL_MODES = [...BRANDED_MODES, ...RUNTIME_MODES, RAW_MODE];
8164
+ function resolveCascadeSlot(pipeline = []) {
8165
+ const normalized = Array.isArray(pipeline) ? pipeline.map((t) => String(t || "").toLowerCase()) : [];
8166
+ if (normalized.includes("brain"))
8167
+ return "brain";
8168
+ if (normalized.includes("medium"))
8169
+ return "medium";
8170
+ return "cheap";
8171
+ }
7897
8172
 
7898
8173
  // src/lib/trinity-tool.js
7899
8174
  var MIN_TOOL_BREAKDOWN_THRESHOLD = 5e-3;
@@ -8177,8 +8452,7 @@ function createTrinityTool(deps) {
8177
8452
  const allEntries = [...BRANDED_MODES, ...RUNTIME_MODES];
8178
8453
  const modeEntry = allEntries.find((e) => e.id === slot);
8179
8454
  if (modeEntry) {
8180
- const rawTier = modeEntry.pipeline[0] || "cheap";
8181
- const tierSlot = (/* @__PURE__ */ new Set(["brain", "medium", "cheap"])).has(rawTier) ? rawTier : "cheap";
8455
+ const tierSlot = resolveCascadeSlot(modeEntry.pipeline);
8182
8456
  deps.writeSessionSlot(deps._OC_SID, tierSlot);
8183
8457
  deps.writeSelection("slot_locked", resolvedSlot !== "auto");
8184
8458
  deps.writeSelection("active_slot", tierSlot);
@@ -9511,12 +9785,12 @@ async function probeModel(modelId, auth, providers = null) {
9511
9785
  }
9512
9786
 
9513
9787
  // src/lib/hooks/footer.js
9514
- import { readFileSync as readFileSync14, appendFileSync as appendFileSync6, mkdirSync as mkdirSync11 } from "node:fs";
9788
+ import { readFileSync as readFileSync14, appendFileSync as appendFileSync4, mkdirSync as mkdirSync10 } from "node:fs";
9515
9789
  import { join as join15 } from "node:path";
9516
9790
 
9517
9791
  // src/lib/hooks/chat-transform.js
9518
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync12, appendFileSync as appendFileSync5, existsSync as existsSync14, mkdirSync as mkdirSync10, rmSync as rmSync5, readdirSync as readdirSync3, statSync as statSync7 } from "node:fs";
9519
- import { join as join14, dirname as dirname10, basename as basename6 } from "node:path";
9792
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync12, appendFileSync as appendFileSync3, existsSync as existsSync14, mkdirSync as mkdirSync9, rmSync as rmSync5, readdirSync as readdirSync3, statSync as statSync7 } from "node:fs";
9793
+ import { join as join14, dirname as dirname10, basename as basename3 } from "node:path";
9520
9794
  import { createHash as createHash3 } from "node:crypto";
9521
9795
 
9522
9796
  // src/lib/mode-policy.js
@@ -9830,6 +10104,11 @@ function noteProjectPattern(kind, key, summary, meta = {}) {
9830
10104
  if (meta.path)
9831
10105
  row.path = meta.path;
9832
10106
  target[key] = row;
10107
+ touchProjectBucket(pstate, currentProjectFingerprint, {
10108
+ sessionId: getCurrentSessionId(),
10109
+ projectName: currentProjectName || "",
10110
+ topic: key
10111
+ });
9833
10112
  const entries = Object.entries(target);
9834
10113
  if (entries.length > 50) {
9835
10114
  entries.sort((a, b) => String(b[1]?.lastSeen || "").localeCompare(String(a[1]?.lastSeen || "")));
@@ -10063,6 +10342,9 @@ function recordSaving(tool2, reason, saveEst, meta = {}) {
10063
10342
  s.last_updated = (/* @__PURE__ */ new Date()).toISOString();
10064
10343
  _pruneOldSessions(s);
10065
10344
  });
10345
+ const projectFingerprint2 = typeof meta?.projectFingerprint === "string" && meta.projectFingerprint.trim() ? meta.projectFingerprint.trim() : currentProjectFingerprint || "";
10346
+ const projectName = typeof meta?.projectName === "string" && meta.projectName.trim() ? meta.projectName.trim() : currentProjectName || "";
10347
+ const sessionId = typeof meta?.sessionId === "string" && meta.sessionId.trim() ? meta.sessionId.trim() : getCurrentSessionId() || _OC_SID;
10066
10348
  const entry = JSON.stringify({
10067
10349
  ts: (/* @__PURE__ */ new Date()).toISOString(),
10068
10350
  usd: saveEst,
@@ -10070,9 +10352,21 @@ function recordSaving(tool2, reason, saveEst, meta = {}) {
10070
10352
  tool: tool2,
10071
10353
  reason,
10072
10354
  saveEst,
10073
- fgp: currentProjectFingerprint || ""
10355
+ fgp: projectFingerprint2
10074
10356
  });
10075
10357
  _ledgerBuffer.push(entry);
10358
+ try {
10359
+ if (projectFingerprint2) {
10360
+ const pstate = loadProjectState();
10361
+ touchProjectBucket(pstate, projectFingerprint2, {
10362
+ sessionId,
10363
+ projectName,
10364
+ topic: tool2 || reason || "saving"
10365
+ });
10366
+ saveProjectState(pstate);
10367
+ }
10368
+ } catch {
10369
+ }
10076
10370
  if (_ledgerBuffer.length >= LEDGER_BUFFER_MAX)
10077
10371
  _flushLedgerBuffer();
10078
10372
  else if (!_ledgerBufferTimer)
@@ -10243,6 +10537,17 @@ function ensureProjectContext(hookDirectory) {
10243
10537
  if (name && name !== currentProjectName)
10244
10538
  setCurrentProjectName(name);
10245
10539
  }
10540
+ try {
10541
+ if (resolved) {
10542
+ const pstate = loadProjectState();
10543
+ touchProjectBucket(pstate, resolved, {
10544
+ sessionId: _OC_SID3,
10545
+ projectName: currentProjectName || (hookDirectory ? hookDirectory.split("/").filter(Boolean).pop() || "" : "")
10546
+ });
10547
+ saveProjectState(pstate);
10548
+ }
10549
+ } catch {
10550
+ }
10246
10551
  return resolved;
10247
10552
  }
10248
10553
  var latestUserIntent = null;
@@ -10303,14 +10608,14 @@ function observeUserCorrection(text) {
10303
10608
  }
10304
10609
  }
10305
10610
  function buildProjectBriefing(directory3) {
10306
- const label = currentProjectName || (directory3 ? basename6(directory3) : "");
10611
+ const label = currentProjectName || (directory3 ? basename3(directory3) : "");
10307
10612
  if (!label)
10308
10613
  return null;
10309
10614
  return `[project memory] Active project: ${label}. Stay focused on the current repository and prefer the existing workflow.`;
10310
10615
  }
10311
10616
  function ensureProjectSkill(dir, fp2) {
10312
10617
  const skillsDir = join14(dir, ".opencode", "skills");
10313
- const projectName = basename6(dir);
10618
+ const projectName = basename3(dir);
10314
10619
  const skillDir = join14(skillsDir, projectName);
10315
10620
  const skillPath = join14(skillDir, "SKILL.md");
10316
10621
  if (existsSync14(skillPath)) {
@@ -10375,7 +10680,7 @@ function ensureProjectSkill(dir, fp2) {
10375
10680
  content += "\n";
10376
10681
  }
10377
10682
  try {
10378
- mkdirSync10(skillDir, { recursive: true });
10683
+ mkdirSync9(skillDir, { recursive: true });
10379
10684
  writeFileSync12(skillPath, content, "utf-8");
10380
10685
  console.error(`[vibeOS] Project Guard: created .opencode/skills/${projectName}/SKILL.md`);
10381
10686
  return { created: true, path: skillPath, skipped: false };
@@ -10534,7 +10839,7 @@ ${raw}
10534
10839
  const sessPath = join14(getSessionScratchpadDir(), `${hash}.txt`);
10535
10840
  const globalPath = join14(globalDir, `${hash}.txt`);
10536
10841
  try {
10537
- mkdirSync10(globalDir, { recursive: true });
10842
+ mkdirSync9(globalDir, { recursive: true });
10538
10843
  ensureSessionScratchpadDirs();
10539
10844
  if (!existsSync14(globalPath)) {
10540
10845
  writeFileSync12(globalPath, raw);
@@ -10947,8 +11252,8 @@ var onSystemTransform = async (_input, output) => {
10947
11252
  fp: currentProjectFingerprint || ""
10948
11253
  }) + "\n";
10949
11254
  try {
10950
- mkdirSync10(calDir, { recursive: true });
10951
- appendFileSync5(calFile, calRecord);
11255
+ mkdirSync9(calDir, { recursive: true });
11256
+ appendFileSync3(calFile, calRecord);
10952
11257
  } catch {
10953
11258
  }
10954
11259
  if (!oneShot("vibeos_dashboard_instruct")) {
@@ -11364,8 +11669,8 @@ ${vibeLine}`;
11364
11669
  tracker.recordOutcome(finalOutcome);
11365
11670
  syncOutcomeToApi(finalOutcome);
11366
11671
  try {
11367
- mkdirSync11(getVibeOSHome10(), { recursive: true });
11368
- appendFileSync6(join15(getVibeOSHome10(), "calibration-data.jsonl"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), event: "outcome", sid: getSessionId(), outcome: finalOutcome }) + "\n");
11672
+ mkdirSync10(getVibeOSHome10(), { recursive: true });
11673
+ appendFileSync4(join15(getVibeOSHome10(), "calibration-data.jsonl"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), event: "outcome", sid: getSessionId(), outcome: finalOutcome }) + "\n");
11369
11674
  } catch {
11370
11675
  }
11371
11676
  }
@@ -11391,8 +11696,8 @@ ${vibeLine} \u2014`);
11391
11696
  }
11392
11697
 
11393
11698
  // src/lib/hooks/tool-execute.js
11394
- import { writeFileSync as writeFileSync14, appendFileSync as appendFileSync8, existsSync as existsSync16, mkdirSync as mkdirSync13 } from "node:fs";
11395
- import { join as join17, dirname as dirname12, basename as basename7 } from "node:path";
11699
+ import { writeFileSync as writeFileSync14, appendFileSync as appendFileSync6, existsSync as existsSync16, mkdirSync as mkdirSync12 } from "node:fs";
11700
+ import { join as join17, dirname as dirname12, basename as basename4 } from "node:path";
11396
11701
  import { createHash as createHash5 } from "node:crypto";
11397
11702
 
11398
11703
  // src/lib/cost-anomaly.js
@@ -11456,7 +11761,7 @@ function getCostAnomalyDetector() {
11456
11761
  init_flow_enforcer();
11457
11762
 
11458
11763
  // src/lib/tdd-enforcer.js
11459
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync13, appendFileSync as appendFileSync7, existsSync as existsSync15, mkdirSync as mkdirSync12, statSync as statSync8, readdirSync as readdirSync4, rmSync as rmSync6, openSync as openSync3 } from "node:fs";
11764
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync13, appendFileSync as appendFileSync5, existsSync as existsSync15, mkdirSync as mkdirSync11, statSync as statSync8, readdirSync as readdirSync4, rmSync as rmSync6, openSync as openSync3 } from "node:fs";
11460
11765
  import { join as join16, dirname as dirname11 } from "node:path";
11461
11766
  import { createHash as createHash4 } from "node:crypto";
11462
11767
 
@@ -12559,7 +12864,7 @@ var COOLDOWN_MS = 6e4;
12559
12864
  var _enforcementCooldown = /* @__PURE__ */ new Set();
12560
12865
  function _acquireLock(testPath) {
12561
12866
  try {
12562
- mkdirSync12(ENFORCEMENT_LOCK_DIR, { recursive: true });
12867
+ mkdirSync11(ENFORCEMENT_LOCK_DIR, { recursive: true });
12563
12868
  const hash = createHash4("sha256").update(testPath).digest("hex").slice(0, 16);
12564
12869
  const lockPath = join16(ENFORCEMENT_LOCK_DIR, `${hash}.lock`);
12565
12870
  try {
@@ -12616,10 +12921,10 @@ function _isInCooldown(testPath) {
12616
12921
  }
12617
12922
  function _recordCooldown(testPath) {
12618
12923
  try {
12619
- mkdirSync12(dirname11(ENFORCEMENT_COOLDOWN_FILE2), { recursive: true });
12924
+ mkdirSync11(dirname11(ENFORCEMENT_COOLDOWN_FILE2), { recursive: true });
12620
12925
  const hash = createHash4("sha256").update(testPath).digest("hex").slice(0, 16);
12621
12926
  const entry = JSON.stringify({ h: hash, ts: Date.now() }) + "\n";
12622
- appendFileSync7(ENFORCEMENT_COOLDOWN_FILE2, entry);
12927
+ appendFileSync5(ENFORCEMENT_COOLDOWN_FILE2, entry);
12623
12928
  const lines = readFileSync15(ENFORCEMENT_COOLDOWN_FILE2, "utf-8").trim().split("\n").filter(Boolean);
12624
12929
  if (lines.length > 500) {
12625
12930
  writeFileSync13(ENFORCEMENT_COOLDOWN_FILE2, lines.slice(-200).join("\n") + "\n");
@@ -12708,7 +13013,7 @@ function enforceTestFile(filePath) {
12708
13013
  if (!_acquireLock(skeleton.path))
12709
13014
  return null;
12710
13015
  try {
12711
- mkdirSync12(skeleton.dir, { recursive: true });
13016
+ mkdirSync11(skeleton.dir, { recursive: true });
12712
13017
  writeFileSync13(skeleton.path, skeleton.content);
12713
13018
  _enforcementCooldown.add(skeleton.path);
12714
13019
  _recordCooldown(skeleton.path);
@@ -12934,7 +13239,7 @@ function _isProtectedToolPath(pathValue) {
12934
13239
  }
12935
13240
  function _mutateBlockedToolArgs(toolName, sources, blockedPath, outputObj) {
12936
13241
  const tLower = String(toolName || "").toLowerCase();
12937
- const blockedBase = basename7(blockedPath || "") || "blocked";
13242
+ const blockedBase = basename4(blockedPath || "") || "blocked";
12938
13243
  for (const src of sources) {
12939
13244
  if (!src || typeof src !== "object")
12940
13245
  continue;
@@ -13243,7 +13548,7 @@ ${argsJson}
13243
13548
  _mutateBlockedToolArgs(t, argSources, checkPath, output);
13244
13549
  if (shouldLogWarn(`${t}|protect|${checkPath}`))
13245
13550
  console.error(`[vibeOS] [protection] BLOCKED direct ${t} in self-protected directory: ${checkPath}`);
13246
- pendingUiNote = `[LOCK] Self-modification paused: ${basename7(checkPath)} is in a protected project tree. Use a manual git workflow.`;
13551
+ pendingUiNote = `[LOCK] Self-modification paused: ${basename4(checkPath)} is in a protected project tree. Use a manual git workflow.`;
13247
13552
  enforcementBlocked = true;
13248
13553
  return;
13249
13554
  }
@@ -13266,7 +13571,12 @@ ${argsJson}
13266
13571
  costDetector.record(modelCost);
13267
13572
  }
13268
13573
  if (_credit < 40 && !compatibilityMode) {
13269
- const total = recordSaving(t, "credit<40% high-tier", _estOpus, { firstWord: _firstWord });
13574
+ const total = recordSaving(t, "credit<40% high-tier", _estOpus, {
13575
+ firstWord: _firstWord,
13576
+ projectFingerprint: currentProjectFingerprint,
13577
+ projectName: currentProjectName || "",
13578
+ sessionId: getCurrentSessionId()
13579
+ });
13270
13580
  const msg = `[vibeOS] Quick win: ${resolveTierIcon("cheap")} cheap lane open \xB7 switch to ${resolveTierIcon("medium")} medium to save about ~$${_estOpus.toFixed(3)}/turn.`;
13271
13581
  if (shouldLogWarn(`${t}|credit|${_tierWord}`) && process.env.VIBEOS_DEBUG_DELEGATION === "1") {
13272
13582
  console.error(`[vibeOS] [delegation] ${msg}`);
@@ -13281,7 +13591,7 @@ ${argsJson}
13281
13591
  const tLower = String(t || "").toLowerCase();
13282
13592
  if (!compatibilityMode && sel.delegation_enforce && currentTier === "high" && argSources.length > 0) {
13283
13593
  const originalPath = argSources.flatMap((src) => [src?.filePath, src?.file_path, src?.path]).find((v) => typeof v === "string" && v.trim()) || "";
13284
- const basename9 = originalPath.split("/").pop() || "blocked";
13594
+ const basename6 = originalPath.split("/").pop() || "blocked";
13285
13595
  const apiResult = await remoteCall("delegateCheck", [tLower, currentTier, currentModel, _prompt], () => ({
13286
13596
  blocked: true,
13287
13597
  savings: _estEdit
@@ -13289,7 +13599,12 @@ ${argsJson}
13289
13599
  const isBlocked = apiResult?.blocked !== false;
13290
13600
  const savings = apiResult?.savings ?? _estEdit;
13291
13601
  if (isBlocked) {
13292
- const total2 = recordSaving(t, "delegation enforced", savings, { firstWord: _firstWord });
13602
+ const total2 = recordSaving(t, "delegation enforced", savings, {
13603
+ firstWord: _firstWord,
13604
+ projectFingerprint: currentProjectFingerprint,
13605
+ projectName: currentProjectName || "",
13606
+ sessionId: getCurrentSessionId()
13607
+ });
13293
13608
  pendingUiNote = `[delegation] This is a good candidate for a Task subagent \u2014 ${resolveTierIcon("brain")} brain handles orchestration, let cheaper tiers do the write/edit. Switch to ${resolveTierIcon("medium")} medium with \`trinity medium\` if you'd rather do it directly.`;
13294
13609
  enforcementBlocked = true;
13295
13610
  if (shouldLogWarn(`${t}|enforced|${_tierWord}`))
@@ -13297,7 +13612,12 @@ ${argsJson}
13297
13612
  return;
13298
13613
  }
13299
13614
  }
13300
- const total = recordSaving(t, "direct edit", _estEdit, { firstWord: _firstWord });
13615
+ const total = recordSaving(t, "direct edit", _estEdit, {
13616
+ firstWord: _firstWord,
13617
+ projectFingerprint: currentProjectFingerprint,
13618
+ projectName: currentProjectName || "",
13619
+ sessionId: getCurrentSessionId()
13620
+ });
13301
13621
  if (!compatibilityMode) {
13302
13622
  const msg = `[vibeOS] ${resolveTierIcon("cheap")} cheap lane \xB7 save about ~$${_estEdit.toFixed(3)} by delegating to Task. Try ${resolveTierIcon("medium")} medium.`;
13303
13623
  if (shouldLogWarn(`${t}|direct|${_tierWord}`) && process.env.VIBEOS_DEBUG_DELEGATION === "1") {
@@ -13321,7 +13641,7 @@ ${argsJson}
13321
13641
  const missed = recordMissedContext7(_estC7);
13322
13642
  if (!existsSync16(CONTEXT7_INSTALL_FLAG)) {
13323
13643
  try {
13324
- mkdirSync13(dirname12(CONTEXT7_INSTALL_FLAG), { recursive: true });
13644
+ mkdirSync12(dirname12(CONTEXT7_INSTALL_FLAG), { recursive: true });
13325
13645
  writeFileSync14(CONTEXT7_INSTALL_FLAG, "");
13326
13646
  } catch {
13327
13647
  }
@@ -13336,7 +13656,11 @@ ${argsJson}
13336
13656
  softQuotaCounts[t] = (softQuotaCounts[t] ?? 0) + 1;
13337
13657
  const n = softQuotaCounts[t];
13338
13658
  if (n === SOFT_QUOTA_LIMIT + 1) {
13339
- const total = recordSaving(t, `soft quota exceeded (limit ${SOFT_QUOTA_LIMIT})`, SAVE_EST.SOFT_QUOTA);
13659
+ const total = recordSaving(t, `soft quota exceeded (limit ${SOFT_QUOTA_LIMIT})`, SAVE_EST.SOFT_QUOTA, {
13660
+ projectFingerprint: currentProjectFingerprint,
13661
+ projectName: currentProjectName || "",
13662
+ sessionId: getCurrentSessionId()
13663
+ });
13340
13664
  console.error(`[vibeOS] Bash usage is getting heavy (${n}/${SOFT_QUOTA_LIMIT}) \u2014 hand the next step to a Task subagent.`);
13341
13665
  }
13342
13666
  return;
@@ -13507,7 +13831,7 @@ var onToolExecuteAfter = async (input, output) => {
13507
13831
  const taskPrompt = input?.args?.prompt || input?.args?.description || "";
13508
13832
  const quality = scoreTaskQuality(taskOutput, taskPrompt);
13509
13833
  try {
13510
- appendFileSync8(SAVINGS_LEDGER_FILE, JSON.stringify({
13834
+ appendFileSync6(SAVINGS_LEDGER_FILE, JSON.stringify({
13511
13835
  at: (/* @__PURE__ */ new Date()).toISOString(),
13512
13836
  kind: "quality",
13513
13837
  score: quality,
@@ -13673,7 +13997,7 @@ ${pendingUiNote}`;
13673
13997
  if (guardRe.test(fp3)) {
13674
13998
  const guardIcons = { flag: "!", warn: "!!", hint: "_" };
13675
13999
  const guardIcon = guardIcons.flag || "!";
13676
- const fn = basename7(fp3);
14000
+ const fn = basename4(fp3);
13677
14001
  console.error(`[flow-enforcer] ${guardIcon} [guard] ${fn}: protected project doc modified \u2014 verify user intent`);
13678
14002
  }
13679
14003
  }
@@ -13981,7 +14305,7 @@ async function _seedModelTiersIfMissing(directory3) {
13981
14305
  cheap: { oc: cheap, cc: modelToCcAlias(cheap) }
13982
14306
  }
13983
14307
  };
13984
- mkdirSync14(dirname13(TIERS_FILE3), { recursive: true });
14308
+ mkdirSync13(dirname13(TIERS_FILE3), { recursive: true });
13985
14309
  writeFileSync15(TIERS_FILE3, JSON.stringify(tiers, null, 2) + "\n", "utf-8");
13986
14310
  return true;
13987
14311
  }
@@ -14050,7 +14374,7 @@ function persistMcpPort(port) {
14050
14374
  tiers.selection.mcp_port = port;
14051
14375
  if ("mcp_port" in tiers)
14052
14376
  delete tiers.mcp_port;
14053
- mkdirSync14(dirname13(getTiersFile()), { recursive: true });
14377
+ mkdirSync13(dirname13(getTiersFile()), { recursive: true });
14054
14378
  const tmp = getTiersFile() + ".tmp." + Date.now();
14055
14379
  writeFileSync15(tmp, JSON.stringify(tiers, null, 2) + "\n", "utf-8");
14056
14380
  renameSync6(tmp, getTiersFile());
@@ -14077,6 +14401,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14077
14401
  setShellDirectory(directory3 || "");
14078
14402
  registerSessionCleanupHandlers();
14079
14403
  pruneScratchpadOnce();
14404
+ runStartupMaintenanceOnce();
14080
14405
  const _bootstrapModel = await _resolveBootstrapModel(client2, directory3);
14081
14406
  if (_bootstrapModel.model) {
14082
14407
  setCurrentModel(_bootstrapModel.model);
@@ -14141,7 +14466,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14141
14466
  };
14142
14467
  const saveProjectStateStable = (state) => {
14143
14468
  try {
14144
- mkdirSync14(dirname13(hookProjectStateFile), { recursive: true });
14469
+ mkdirSync13(dirname13(hookProjectStateFile), { recursive: true });
14145
14470
  const tmp = hookProjectStateFile + ".tmp";
14146
14471
  writeFileSync15(tmp, JSON.stringify(state, null, 2) + "\n");
14147
14472
  renameSync6(tmp, hookProjectStateFile);
@@ -14160,7 +14485,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14160
14485
  };
14161
14486
  const saveReportsIndexStable = (idx) => {
14162
14487
  try {
14163
- mkdirSync14(hookReportsDir, { recursive: true });
14488
+ mkdirSync13(hookReportsDir, { recursive: true });
14164
14489
  writeFileSync15(hookReportsIndex, JSON.stringify(idx, null, 2) + "\n");
14165
14490
  } catch {
14166
14491
  }
@@ -14170,9 +14495,9 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14170
14495
  if (!existsSync18(path))
14171
14496
  return null;
14172
14497
  const bkDir = join18(hookVibeHome, ".backups");
14173
- mkdirSync14(bkDir, { recursive: true });
14174
- const bk = join18(bkDir, `${basename8(path)}.${label}.${Date.now()}.bak`);
14175
- copyFileSync5(path, bk);
14498
+ mkdirSync13(bkDir, { recursive: true });
14499
+ const bk = join18(bkDir, `${basename5(path)}.${label}.${Date.now()}.bak`);
14500
+ copyFileSync2(path, bk);
14176
14501
  return bk;
14177
14502
  } catch {
14178
14503
  return null;
@@ -14210,7 +14535,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
14210
14535
  writeFileSync: writeFileSync15,
14211
14536
  existsSync: existsSync18,
14212
14537
  renameSync: renameSync6,
14213
- mkdirSync: mkdirSync14,
14538
+ mkdirSync: mkdirSync13,
14214
14539
  get TIERS_FILE() {
14215
14540
  return hookTiersFile;
14216
14541
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeostheog",
3
- "version": "0.24.16",
3
+ "version": "0.24.18",
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",