vibeostheog 0.15.34 → 0.18.3
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 +27 -0
- package/README.md +40 -1
- package/package.json +1 -1
- package/src/index.js +231 -110
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
## 0.18.3
|
|
2
|
+
- feat: dynamic mode injection + footer hooks fix
|
|
3
|
+
- fix: auto-enable plugin on load + always show footer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## 0.17.0
|
|
7
|
+
- feat: universal context7 detection — scans local opencode.json, ~/.config/opencode/*.json, system PATH, and npm npx cache
|
|
8
|
+
- feat: `_scanOpenCodeConfigs` — finds context7 in any JSON config under ~/.config/opencode/
|
|
9
|
+
- feat: `_context7InPath` — detects context7 binary in system PATH
|
|
10
|
+
- feat: `_context7InNpmCache` — detects context7 in npx cached installations
|
|
11
|
+
- fix: local project opencode.json added to CONTEXT7_CONFIG_FILES search list
|
|
12
|
+
|
|
13
|
+
## 0.16.0
|
|
14
|
+
- feat: dopamine-style footer + natural language system directives
|
|
15
|
+
- feat: dopamine-style footer + natural language system directives
|
|
16
|
+
- feat: turn-aware compaction directive at turn 7+
|
|
17
|
+
- feat: add forensic/web-research modes + 1084-datapoint benchmark
|
|
18
|
+
- fix: flash icon only when API connected, unified [VIBE→MODE⚡] format
|
|
19
|
+
- docs: Security section + Context7 cost optimization docs
|
|
20
|
+
- docs: add Security section with API token emphasis and Context7 cost optimization docs
|
|
21
|
+
- docs: persist all benchmark data + compaction research
|
|
22
|
+
- docs: reformat README as user-facing PM doc, move internals to AGENTS.md, cleanup .gitignore
|
|
23
|
+
Merge pull request #35 from DrunkkToys/refactor/simplify-chat-transform
|
|
24
|
+
readme: center VIBE autoswitching as the core value proposition
|
|
25
|
+
Revert "fix: add system prompt cache savings tracking"
|
|
26
|
+
|
|
27
|
+
|
|
1
28
|
## 0.15.23
|
|
2
29
|
- feat: add trinity api-token command to inject VIBEOS_API_TOKEN
|
|
3
30
|
|
package/README.md
CHANGED
|
@@ -103,12 +103,51 @@ npm run dashboard # Start server on http://127.0.0.1:3333
|
|
|
103
103
|
|
|
104
104
|
Displays model split, savings, session history, stress gauge, trinity controls, reports, and blackbox state with SSE push updates every 1.5s.
|
|
105
105
|
|
|
106
|
+
## Security
|
|
107
|
+
|
|
108
|
+
The API token (`VIBEOS_API_TOKEN`) acts as a **password** for your vibeOS seat. Treat it with the same care as any credential.
|
|
109
|
+
|
|
110
|
+
- **Token-based seat auth** — Each token is bound to a seat. Suspending a seat immediately revokes all associated tokens.
|
|
111
|
+
- **Graceful degradation** — If the token is revoked or the API is unreachable, the plugin falls back to local-only mode with bundled algorithms. No functionality is lost; only remote-optimized routing is disabled.
|
|
112
|
+
- **Never commit tokens** — `.env.production` and `PRODUCTION-CREDENTIALS.md` are gitignored. Do not share or hardcode tokens in source files.
|
|
113
|
+
- **Token rotation** — Generate a new token and update `VIBEOS_API_TOKEN` if you suspect a leak. Old tokens are invalidated immediately on seat suspension.
|
|
114
|
+
- **Local-only mode** — Without an API token, all algorithms run locally. Set `VIBEOS_API_ENABLED=false` to explicitly disable all remote calls.
|
|
115
|
+
|
|
116
|
+
## Context7 Cost Optimization
|
|
117
|
+
|
|
118
|
+
[context7](https://upstash.com/docs/context7) is an MCP tool that resolves library/framework documentation queries at a fraction of the cost of WebFetch (~$0.06/turn saved).
|
|
119
|
+
|
|
120
|
+
**Install it once:**
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
claude mcp add context7 npx @upstash/context7-mcp
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### How vibeOS uses context7
|
|
127
|
+
|
|
128
|
+
- **Auto-detection** — At module load, vibeOS scans `~/.claude/settings.json`, `~/.claude.json`, `opencode.json`, and `~/.config/opencode/` for a `context7` reference. No manual config needed.
|
|
129
|
+
- **System prompt injection** — When context7 is detected, a cost-policy directive is injected into every system prompt instructing the model to prefer `mcp__context7__resolve-library-id` and `mcp__context7__get-library-docs` over WebFetch/WebSearch for documentation URLs.
|
|
130
|
+
- **Urgency levels** — Controlled by the blackbox engine:
|
|
131
|
+
- `required` (strict/TDD-strict mode) — context7 is mandatory this turn.
|
|
132
|
+
- `preferred` (default) — context7 is encouraged but not forced.
|
|
133
|
+
- `optional` (relaxed mode) — context7 is a nice-to-have.
|
|
134
|
+
- **Docs nudge** — If context7 is not installed and the model uses WebFetch on a documentation URL (docs.*, readthedocs, MDN, npmjs, pypi, crates.io, pkg.go.dev, etc.), vibeOS logs a one-time install suggestion and tracks the missed savings.
|
|
135
|
+
- **Savings tracking** — Every docs URL fetched via WebFetch instead of context7 is recorded as `missed_context7_usd` in `~/.claude/delegation-state.json`. Accumulated misses appear in `trinity project` analytics with an installation suggestion when bypasses exceed 3.
|
|
136
|
+
|
|
137
|
+
### Force-enable detection
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
export CLAUDE_CONTEXT7_AVAILABLE=true
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Use this when context7 is configured but the auto-scan misses it (unusual paths, remote configs, or runtime-loaded MCP definitions).
|
|
144
|
+
|
|
106
145
|
## Environment Variables
|
|
107
146
|
|
|
108
147
|
| Variable | Default | Description |
|
|
109
148
|
|---|---|---|
|
|
110
149
|
| `VIBEOS_API_URL` | `https://api.vibetheog.com` | API server URL |
|
|
111
|
-
| `VIBEOS_API_TOKEN` | — | API token (
|
|
150
|
+
| `VIBEOS_API_TOKEN` | — | **API token (password). Protect like a credential.** Required for remote mode. If compromised, rotate immediately via seat suspension. |
|
|
112
151
|
| `VIBEOS_API_ENABLED` | `true` | Set to `false` for local-only mode |
|
|
113
152
|
| `CLAUDE_CREDIT_PERCENT` | `100` | Credit percentage override |
|
|
114
153
|
| `CLAUDE_CONTEXT7_AVAILABLE` | — | Enable context7 cost optimization |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeostheog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.3",
|
|
4
4
|
"description": "Cost-aware delegation enforcer for OpenCode. Tracks model usage, routes Task subagents to cheaper tiers, surfaces cumulative savings in chat. Includes research audit, reporting framework, project memory, progressive scratchpad decadence, and trinity CLI for brain/medium/cheap slot switching.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"release": "node scripts/release.mjs",
|
package/src/index.js
CHANGED
|
@@ -383,8 +383,8 @@ var init_flow_enforcer = __esm({
|
|
|
383
383
|
|
|
384
384
|
// src/index.ts
|
|
385
385
|
init_flow_enforcer();
|
|
386
|
-
import { readFileSync as readFileSync15, writeFileSync as
|
|
387
|
-
import { join as join15, dirname as
|
|
386
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, existsSync as existsSync14, mkdirSync as mkdirSync11, copyFileSync as copyFileSync5, renameSync as renameSync6 } from "node:fs";
|
|
387
|
+
import { join as join15, dirname as dirname8, basename as basename8 } from "node:path";
|
|
388
388
|
|
|
389
389
|
// src/vibeOS-lib/session-metrics.js
|
|
390
390
|
function formatDuration(totalSeconds) {
|
|
@@ -2733,7 +2733,7 @@ var MODEL_USD_PER_TURN = {
|
|
|
2733
2733
|
"haiku": 22e-4,
|
|
2734
2734
|
// ── DeepSeek (OC platform + OpenRouter) ──────────────────
|
|
2735
2735
|
"deepseek/deepseek-v4-pro": 57e-5,
|
|
2736
|
-
"deepseek/deepseek-v4-flash":
|
|
2736
|
+
"deepseek/deepseek-v4-flash": 0.00013,
|
|
2737
2737
|
"deepseek/deepseek-chat": 182e-6,
|
|
2738
2738
|
"deepseek-chat": 182e-6,
|
|
2739
2739
|
"deepseek/deepseek-v3": 182e-6,
|
|
@@ -2893,6 +2893,41 @@ function _scanOpenCodeConfigs(baseDir) {
|
|
|
2893
2893
|
}
|
|
2894
2894
|
return false;
|
|
2895
2895
|
}
|
|
2896
|
+
function _context7InPath() {
|
|
2897
|
+
try {
|
|
2898
|
+
const pathDirs = (process.env.PATH || "").split(":");
|
|
2899
|
+
for (const dir of pathDirs) {
|
|
2900
|
+
if (!dir)
|
|
2901
|
+
continue;
|
|
2902
|
+
try {
|
|
2903
|
+
if (existsSync4(join4(dir, "context7")))
|
|
2904
|
+
return true;
|
|
2905
|
+
if (existsSync4(join4(dir, "context7.cmd")))
|
|
2906
|
+
return true;
|
|
2907
|
+
} catch {
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
} catch {
|
|
2911
|
+
}
|
|
2912
|
+
return false;
|
|
2913
|
+
}
|
|
2914
|
+
function _context7InNpmCache() {
|
|
2915
|
+
try {
|
|
2916
|
+
const npxDir = join4(USER_HOME3, ".npm/_npx");
|
|
2917
|
+
if (!existsSync4(npxDir))
|
|
2918
|
+
return false;
|
|
2919
|
+
for (const hashDir of readdirSync2(npxDir)) {
|
|
2920
|
+
const ctxDir = join4(npxDir, hashDir, "node_modules", "context7");
|
|
2921
|
+
try {
|
|
2922
|
+
if (existsSync4(join4(ctxDir, "package.json")))
|
|
2923
|
+
return true;
|
|
2924
|
+
} catch {
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
} catch {
|
|
2928
|
+
}
|
|
2929
|
+
return false;
|
|
2930
|
+
}
|
|
2896
2931
|
function detectContext7(files = CONTEXT7_CONFIG_FILES) {
|
|
2897
2932
|
if (process.env.CLAUDE_CONTEXT7_AVAILABLE)
|
|
2898
2933
|
return true;
|
|
@@ -2905,6 +2940,10 @@ function detectContext7(files = CONTEXT7_CONFIG_FILES) {
|
|
|
2905
2940
|
}
|
|
2906
2941
|
if (_scanOpenCodeConfigs(join4(USER_HOME3, ".config/opencode")))
|
|
2907
2942
|
return true;
|
|
2943
|
+
if (_context7InPath())
|
|
2944
|
+
return true;
|
|
2945
|
+
if (_context7InNpmCache())
|
|
2946
|
+
return true;
|
|
2908
2947
|
return false;
|
|
2909
2948
|
}
|
|
2910
2949
|
var DOCS_TARGET_RE = /(docs\.|readthedocs|developer\.mozilla|\/api\/|\/reference\/|\/guide\/|npmjs\.com\/package\/|pypi\.org\/project\/|crates\.io\/crates\/|pkg\.go\.dev|api-docs|\/javadoc\/)/i;
|
|
@@ -5126,13 +5165,13 @@ async function probeModel(modelId, auth) {
|
|
|
5126
5165
|
}
|
|
5127
5166
|
}
|
|
5128
5167
|
|
|
5129
|
-
// src/lib/hooks/footer.
|
|
5130
|
-
import { readFileSync as readFileSync12 } from "node:fs";
|
|
5168
|
+
// src/lib/hooks/footer.js
|
|
5169
|
+
import { readFileSync as readFileSync12, appendFileSync as appendFileSync6, mkdirSync as mkdirSync8 } from "node:fs";
|
|
5131
5170
|
import { join as join13 } from "node:path";
|
|
5132
5171
|
import { homedir as homedir9, tmpdir as tmpdir6 } from "node:os";
|
|
5133
5172
|
|
|
5134
5173
|
// src/lib/hooks/chat-transform.js
|
|
5135
|
-
import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "node:fs";
|
|
5174
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, appendFileSync as appendFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "node:fs";
|
|
5136
5175
|
import { join as join12, basename as basename6 } from "node:path";
|
|
5137
5176
|
import { homedir as homedir8 } from "node:os";
|
|
5138
5177
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -5545,7 +5584,7 @@ async function apiComputeControlVector(state, action, optimizationMode) {
|
|
|
5545
5584
|
}
|
|
5546
5585
|
const opt = (optimizationMode || "balanced").toLowerCase();
|
|
5547
5586
|
const isRelaxed = opt === "budget" || opt === "speed" || opt === "audit";
|
|
5548
|
-
const isStrict = opt === "quality";
|
|
5587
|
+
const isStrict = opt === "quality" || opt === "forensic" || opt === "defense_in_depth" || opt === "reporting";
|
|
5549
5588
|
return {
|
|
5550
5589
|
enforcement_mode: isStrict ? "strict" : "normal",
|
|
5551
5590
|
enforcement_reason: `[optimize: ${opt}] offline fallback`,
|
|
@@ -6065,6 +6104,20 @@ var onSystemTransform = async (_input, output) => {
|
|
|
6065
6104
|
}
|
|
6066
6105
|
}
|
|
6067
6106
|
pushSystem(output, "[project guard: CRITICAL] AGENTS.md and README.md are protected by vibeOS. Do NOT modify either file without explicit user permission. When implementing new features, update README.md to document them. AGENTS.md defines that AI agents must ask before changing code -- respect this rule.");
|
|
6107
|
+
const currentMode = loadOptimizationMode();
|
|
6108
|
+
if (currentMode === "quality") {
|
|
6109
|
+
pushSystem(output, "[mode: quality] Prioritize quality and thoroughness. Provide complete edge case coverage, comprehensive error handling, full type annotations, production-grade code with tests. Do not cut corners for brevity.");
|
|
6110
|
+
} else if (currentMode === "forensic") {
|
|
6111
|
+
pushSystem(output, "[mode: forensic] Use forensic analysis depth: evidence-based reasoning tracing each claim to source; multi-hypothesis evaluation considering competing explanations; explicit uncertainty flags for assumptions and trade-offs; structured output with clear reasoning traces; thorough verification of all edge cases and failure modes.");
|
|
6112
|
+
} else if (currentMode === "web-research" || currentMode === "exploration") {
|
|
6113
|
+
pushSystem(output, "[mode: exploration] Use a research-oriented approach: gather information from multiple perspectives before converging; document alternative approaches; flag confidence levels for each finding; synthesize into actionable recommendations.");
|
|
6114
|
+
} else if (currentMode === "defense_in_depth") {
|
|
6115
|
+
pushSystem(output, "[mode: defense in depth] For every component: define the threat model, implement with defenses, then verify the defense handles the threat. Never write code without specifying what it defends against. Consider: injection, broken auth, data exposure, logic errors, race conditions.");
|
|
6116
|
+
} else if (currentMode === "reporting") {
|
|
6117
|
+
pushSystem(output, "[mode: formal report] Structure output as a formal engineering report with: executive summary, methodology, detailed findings with evidence, trade-offs documented, conclusions with confidence levels, and recommendations.");
|
|
6118
|
+
} else if (currentMode === "verify") {
|
|
6119
|
+
pushSystem(output, "[mode: verification-first] Before writing code, declare verification criteria: which edge cases must pass, what invariants must hold. After each code block, include a verification section showing how correctness is established against each criterion.");
|
|
6120
|
+
}
|
|
6068
6121
|
pushSystem(output, contextBudgetDirective(_input, output));
|
|
6069
6122
|
if (!oneShot(fp2)) {
|
|
6070
6123
|
pushSystem(output, buildProjectBriefing(currentProjectName || ""));
|
|
@@ -6075,21 +6128,55 @@ var onSystemTransform = async (_input, output) => {
|
|
|
6075
6128
|
if (!oneShot("trinity_welcome_" + fp2)) {
|
|
6076
6129
|
pushSystem(output, welcomeDirective());
|
|
6077
6130
|
}
|
|
6131
|
+
const calDir = join12(homedir8(), ".claude");
|
|
6132
|
+
const calFile = join12(calDir, "calibration-data.jsonl");
|
|
6133
|
+
const regime = _latestBlackboxState3?.sub_regime || classifyTurnSimple(latestUserIntent || "");
|
|
6134
|
+
const calRecord = JSON.stringify({
|
|
6135
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6136
|
+
sid: _OC_SID4,
|
|
6137
|
+
mode: currentMode,
|
|
6138
|
+
regime,
|
|
6139
|
+
stress: stressScore,
|
|
6140
|
+
fp: currentProjectFingerprint || ""
|
|
6141
|
+
}) + "\n";
|
|
6142
|
+
try {
|
|
6143
|
+
mkdirSync7(calDir, { recursive: true });
|
|
6144
|
+
appendFileSync5(calFile, calRecord);
|
|
6145
|
+
} catch {
|
|
6146
|
+
}
|
|
6078
6147
|
if (!oneShot("vibeos_dashboard_instruct")) {
|
|
6079
6148
|
pushSystem(output, "[vibeOS dashboard display] When the trinity tool returns output starting with '[vibeOS-dashboard]', you MUST use the question tool to display that data in a clean, human-readable format. Use the question field (not the header) to show the dashboard data. Format it with clear sections separated by blank lines, aligned columns with spaces, and plain text only (no emojis, no markdown). The header should be 'vibeOS Dashboard'. Include only one option in options: {label: 'Dismiss', description: ''}. Strip the '[vibeOS-dashboard]' marker line before displaying.");
|
|
6080
6149
|
}
|
|
6150
|
+
if (!oneShot("vibeos_dopamine_style_" + fp2)) {
|
|
6151
|
+
pushSystem(output, "[tool style: dopamine] When calling the bash tool, use an emoji-prefixed, progress-focused description \u2014 e.g. 'Shell \u26A1 Compiling assets...' or 'Shell \u{1F9EA} Running tests...'. Combine independent bash commands into a single call with && or ;. Never use raw technical labels as tool descriptions.");
|
|
6152
|
+
}
|
|
6081
6153
|
} catch (err) {
|
|
6082
6154
|
console.error(`[vibeOS] system.transform failed: ${err.message}`);
|
|
6083
6155
|
}
|
|
6084
6156
|
};
|
|
6085
6157
|
|
|
6086
|
-
// src/lib/hooks/footer.
|
|
6158
|
+
// src/lib/hooks/footer.js
|
|
6087
6159
|
var _cachedAutoMode = null;
|
|
6088
6160
|
var _cachedAutoModeTs = 0;
|
|
6089
6161
|
var AUTO_CACHE_TTL = 6e4;
|
|
6162
|
+
var DEFAULT_REGIME_MAP = {
|
|
6163
|
+
LOOPING: "forensic",
|
|
6164
|
+
DIVERGENT: "forensic",
|
|
6165
|
+
EXPLORING: "web-research",
|
|
6166
|
+
INIT: "web-research",
|
|
6167
|
+
REFINING: "balanced",
|
|
6168
|
+
CONVERGING: "quality",
|
|
6169
|
+
CLOSED: "quality"
|
|
6170
|
+
};
|
|
6171
|
+
function regimeToMode(regime, stress) {
|
|
6172
|
+
if (stress > 1.5)
|
|
6173
|
+
return "quality";
|
|
6174
|
+
return DEFAULT_REGIME_MAP[regime] || "balanced";
|
|
6175
|
+
}
|
|
6090
6176
|
async function apiAutoSelectMode(regime, stress) {
|
|
6091
6177
|
const now = Date.now();
|
|
6092
|
-
if (_cachedAutoMode && now - _cachedAutoModeTs < AUTO_CACHE_TTL)
|
|
6178
|
+
if (_cachedAutoMode && now - _cachedAutoModeTs < AUTO_CACHE_TTL)
|
|
6179
|
+
return _cachedAutoMode;
|
|
6093
6180
|
try {
|
|
6094
6181
|
const res = await remoteCall("blackboxSelectMode", [regime, stress], null);
|
|
6095
6182
|
if (res?.mode) {
|
|
@@ -6100,7 +6187,10 @@ async function apiAutoSelectMode(regime, stress) {
|
|
|
6100
6187
|
} catch (e) {
|
|
6101
6188
|
console.error("[vibeOS] apiAutoSelectMode error:", e.message);
|
|
6102
6189
|
}
|
|
6103
|
-
|
|
6190
|
+
const fallback = regimeToMode(regime, stress);
|
|
6191
|
+
if (!_cachedAutoMode || _cachedAutoMode === "balanced")
|
|
6192
|
+
_cachedAutoMode = fallback;
|
|
6193
|
+
return _cachedAutoMode || fallback || "balanced";
|
|
6104
6194
|
}
|
|
6105
6195
|
var USER_HOME6 = (() => {
|
|
6106
6196
|
try {
|
|
@@ -6149,24 +6239,33 @@ function readLifetimeSavings2() {
|
|
|
6149
6239
|
}
|
|
6150
6240
|
var _OC_SID5 = "opencode-" + (process.pid || "x") + "-" + Date.now();
|
|
6151
6241
|
function scoreTaskQuality(outputText, promptText) {
|
|
6152
|
-
if (typeof outputText !== "string" || outputText.length === 0)
|
|
6153
|
-
|
|
6242
|
+
if (typeof outputText !== "string" || outputText.length === 0)
|
|
6243
|
+
return 0;
|
|
6244
|
+
if (typeof promptText !== "string")
|
|
6245
|
+
promptText = "";
|
|
6154
6246
|
let score = 50;
|
|
6155
|
-
if (promptText.length > 0 && outputText.length > promptText.length * 0.5)
|
|
6156
|
-
|
|
6157
|
-
if (
|
|
6158
|
-
|
|
6247
|
+
if (promptText.length > 0 && outputText.length > promptText.length * 0.5)
|
|
6248
|
+
score += 10;
|
|
6249
|
+
if (outputText.length < 50)
|
|
6250
|
+
score -= 20;
|
|
6251
|
+
if (/error|failed|unable|cannot|could not/i.test(outputText))
|
|
6252
|
+
score -= 10;
|
|
6253
|
+
if (/TODO|FIXME|placeholder/i.test(outputText) && outputText.length < 200)
|
|
6254
|
+
score -= 15;
|
|
6159
6255
|
const codeBlocks = (outputText.match(/```/g) || []).length;
|
|
6160
|
-
if (codeBlocks >= 2)
|
|
6161
|
-
|
|
6162
|
-
if (outputText.length >
|
|
6256
|
+
if (codeBlocks >= 2)
|
|
6257
|
+
score += 10;
|
|
6258
|
+
if (outputText.length > 500)
|
|
6259
|
+
score += 10;
|
|
6260
|
+
if (outputText.length > 1e3)
|
|
6261
|
+
score += 5;
|
|
6163
6262
|
return Math.max(0, Math.min(100, score));
|
|
6164
6263
|
}
|
|
6165
6264
|
async function _appendFooter(input, output, directory3) {
|
|
6166
|
-
if (!loadSelection3().enabled) return;
|
|
6167
6265
|
_refreshModel(directory3);
|
|
6168
6266
|
let _footerStress = 0;
|
|
6169
|
-
if (latestUserIntent)
|
|
6267
|
+
if (latestUserIntent)
|
|
6268
|
+
_footerStress = scoreStress(latestUserIntent);
|
|
6170
6269
|
if (!currentModel) {
|
|
6171
6270
|
try {
|
|
6172
6271
|
const cfg = await client.config.get("model");
|
|
@@ -6180,10 +6279,12 @@ async function _appendFooter(input, output, directory3) {
|
|
|
6180
6279
|
}
|
|
6181
6280
|
try {
|
|
6182
6281
|
const messageID = input?.messageID || input?.messageId || input?.message?.id || output?.messageID || output?.messageId || output?.message?.id || null;
|
|
6183
|
-
if (messageID && textCompletePainted.has(messageID))
|
|
6282
|
+
if (messageID && textCompletePainted.has(messageID))
|
|
6283
|
+
return;
|
|
6184
6284
|
const text = typeof output?.text === "string" ? output.text : typeof output?.result === "string" ? output.result : typeof output?.content === "string" ? output.content : "";
|
|
6185
|
-
if (!text
|
|
6186
|
-
if (messageID)
|
|
6285
|
+
if (!text) {
|
|
6286
|
+
if (messageID)
|
|
6287
|
+
textCompletePainted.add(messageID);
|
|
6187
6288
|
return;
|
|
6188
6289
|
}
|
|
6189
6290
|
const { ltTasks, ltCache, ltCost, count, sesTasks, sesEdit, sesCredit, sesC7, sesQuota, sesCache, sesTaskDelegations, sesDuration, sesRatePerHour, sesTrend, sesToolBreakdown, sesModelTurns, quality_avg } = readLifetimeSavings2();
|
|
@@ -6233,12 +6334,17 @@ async function _appendFooter(input, output, directory3) {
|
|
|
6233
6334
|
if (bbMode === "relaxed") {
|
|
6234
6335
|
enfTagsFooter.push("[Q&A]");
|
|
6235
6336
|
} else {
|
|
6236
|
-
if (selNowFooter.delegation_enforce)
|
|
6237
|
-
|
|
6238
|
-
if (selNowFooter.
|
|
6239
|
-
|
|
6337
|
+
if (selNowFooter.delegation_enforce)
|
|
6338
|
+
enfTagsFooter.push("[ENF ON]");
|
|
6339
|
+
if (selNowFooter.flow_enforce)
|
|
6340
|
+
enfTagsFooter.push("[FLOW ON]");
|
|
6341
|
+
if (selNowFooter.tdd_enforce)
|
|
6342
|
+
enfTagsFooter.push("[TDD ON]");
|
|
6343
|
+
if (bbMode === "strict")
|
|
6344
|
+
enfTagsFooter.push("[STRICT]");
|
|
6240
6345
|
}
|
|
6241
|
-
if (_modelLocked)
|
|
6346
|
+
if (_modelLocked)
|
|
6347
|
+
enfTagsFooter.push("[LOCK ON]");
|
|
6242
6348
|
let enfSuffixFooter = enfTagsFooter.length > 0 ? ` ${enfTagsFooter.join(" ")}` : "";
|
|
6243
6349
|
if (quality_avg > 0) {
|
|
6244
6350
|
enfSuffixFooter = ` QA:${Math.round(quality_avg)}% ${enfTagsFooter.join(" ")}`;
|
|
@@ -6251,7 +6357,7 @@ async function _appendFooter(input, output, directory3) {
|
|
|
6251
6357
|
const autoActive = await apiAutoSelectMode(autoRegime, autoStress);
|
|
6252
6358
|
optTagFooter = `[VIBE\u2192${autoActive.toUpperCase()}${flashIcon}]`;
|
|
6253
6359
|
saveOptimizationMode(autoActive);
|
|
6254
|
-
const slot2 = autoActive === "quality" ? "brain" : autoActive === "speed" ? "medium" : "cheap";
|
|
6360
|
+
const slot2 = autoActive === "quality" || autoActive === "forensic" || autoActive === "defense_in_depth" || autoActive === "reporting" ? "brain" : autoActive === "speed" || autoActive === "web-research" || autoActive === "verify" ? "medium" : "cheap";
|
|
6255
6361
|
if (!_modelLocked) {
|
|
6256
6362
|
writeSessionSlot(_OC_SID5, slot2);
|
|
6257
6363
|
if (slot2 === "brain" && TRINITY_BRAIN) {
|
|
@@ -6268,33 +6374,34 @@ async function _appendFooter(input, output, directory3) {
|
|
|
6268
6374
|
} else {
|
|
6269
6375
|
optTagFooter = `[VIBE\u2192${(optModeFooter || "").toUpperCase()}${flashIcon}]`;
|
|
6270
6376
|
}
|
|
6271
|
-
modelTag = `${modelTag}${optTagFooter}${enfSuffixFooter || ""}`;
|
|
6272
6377
|
const stripped = text.replace(/\n\n— .+(?: —)?$/, "");
|
|
6273
|
-
if (stripped !== text)
|
|
6378
|
+
if (stripped !== text)
|
|
6379
|
+
return;
|
|
6274
6380
|
const ltTotal = ltTasks + ltCache;
|
|
6275
|
-
const
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6381
|
+
const modeVerbMap = {
|
|
6382
|
+
balanced: "routing",
|
|
6383
|
+
budget: "saving",
|
|
6384
|
+
quality: "focusing",
|
|
6385
|
+
speed: "moving",
|
|
6386
|
+
longrun: "pacing",
|
|
6387
|
+
auto: "vibing",
|
|
6388
|
+
"web-research": "researching",
|
|
6389
|
+
forensic: "investigating"
|
|
6390
|
+
};
|
|
6391
|
+
const optMode = (optModeFooter || "balanced").toLowerCase();
|
|
6392
|
+
const modeVerb = modeVerbMap[optMode] || "vibing";
|
|
6393
|
+
let vibeLine = `\u2014 ${modeVerb} on ${shortModelName(brainModel)}`;
|
|
6280
6394
|
if (ltTotal > 0) {
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
}
|
|
6288
|
-
const stressBar = _footerStress > 0.85 ? "\u2588" : _footerStress > 0.7 ? "\u2586" : _footerStress > 0.5 ? "\u2585" : _footerStress > 0.3 ? "\u2583" : _footerStress > 0.1 ? "\u2582" : "\u2581";
|
|
6289
|
-
const stressLabel = _footerStress > 0.7 ? "high" : _footerStress > 0.4 ? "elevated" : "calm";
|
|
6290
|
-
footerText = stripped + `
|
|
6291
|
-
|
|
6292
|
-
\u2014 ${modelTag} | ${savingsDisplay} | stress: ${stressBar} ${stressLabel} \u2014`;
|
|
6293
|
-
} else {
|
|
6294
|
-
footerText = stripped + `
|
|
6295
|
-
|
|
6296
|
-
\u2014 ${modelTag} \u2014`;
|
|
6395
|
+
vibeLine += ` \u2728 $${formatUsd(ltTotal)} saved`;
|
|
6396
|
+
}
|
|
6397
|
+
vibeLine += `, VIBE${flashIcon ? " \u26A1" : ""}`;
|
|
6398
|
+
if (_footerStress > 0.4) {
|
|
6399
|
+
const stressLabel = _footerStress > 0.7 ? "elevated" : "uneven";
|
|
6400
|
+
vibeLine += ` \xB7 ${stressLabel}`;
|
|
6297
6401
|
}
|
|
6402
|
+
const footerText = stripped + `
|
|
6403
|
+
|
|
6404
|
+
${vibeLine} \u2014`;
|
|
6298
6405
|
if (_blackboxEnabled) {
|
|
6299
6406
|
try {
|
|
6300
6407
|
const prevText = _prevOutputText;
|
|
@@ -6305,19 +6412,29 @@ async function _appendFooter(input, output, directory3) {
|
|
|
6305
6412
|
const tracker = getBlackboxTracker();
|
|
6306
6413
|
tracker.recordOutcome(outcome);
|
|
6307
6414
|
syncOutcomeToApi(outcome);
|
|
6415
|
+
try {
|
|
6416
|
+
mkdirSync8(join13(USER_HOME6, ".claude"), { recursive: true });
|
|
6417
|
+
appendFileSync6(join13(USER_HOME6, ".claude", "calibration-data.jsonl"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), event: "outcome", sid: _OC_SID5, outcome }) + "\n");
|
|
6418
|
+
} catch {
|
|
6419
|
+
}
|
|
6308
6420
|
}
|
|
6309
6421
|
}
|
|
6310
6422
|
} catch {
|
|
6311
6423
|
}
|
|
6312
6424
|
}
|
|
6313
|
-
if (typeof output?.text === "string")
|
|
6314
|
-
|
|
6315
|
-
else if (typeof output?.
|
|
6316
|
-
|
|
6425
|
+
if (typeof output?.text === "string")
|
|
6426
|
+
output.text = footerText;
|
|
6427
|
+
else if (typeof output?.result === "string")
|
|
6428
|
+
output.result = footerText;
|
|
6429
|
+
else if (typeof output?.content === "string")
|
|
6430
|
+
output.content = footerText;
|
|
6431
|
+
else
|
|
6432
|
+
output.text = footerText;
|
|
6317
6433
|
textCompletePainted.add(messageID);
|
|
6318
6434
|
if (textCompletePainted.size > 500) {
|
|
6319
6435
|
const it = textCompletePainted.values();
|
|
6320
|
-
for (let i = 0; i < 100; i++)
|
|
6436
|
+
for (let i = 0; i < 100; i++)
|
|
6437
|
+
textCompletePainted.delete(it.next().value);
|
|
6321
6438
|
}
|
|
6322
6439
|
} catch (err) {
|
|
6323
6440
|
console.error(`[vibeOS] footer failed: ${err.message}`);
|
|
@@ -6325,13 +6442,13 @@ async function _appendFooter(input, output, directory3) {
|
|
|
6325
6442
|
}
|
|
6326
6443
|
|
|
6327
6444
|
// src/lib/hooks/tool-execute.js
|
|
6328
|
-
import { writeFileSync as
|
|
6329
|
-
import { dirname as
|
|
6445
|
+
import { writeFileSync as writeFileSync11, appendFileSync as appendFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync10 } from "node:fs";
|
|
6446
|
+
import { dirname as dirname7, basename as basename7 } from "node:path";
|
|
6330
6447
|
init_flow_enforcer();
|
|
6331
6448
|
|
|
6332
6449
|
// src/lib/tdd-enforcer.js
|
|
6333
|
-
import { readFileSync as readFileSync13, writeFileSync as
|
|
6334
|
-
import { join as join14, dirname as
|
|
6450
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, appendFileSync as appendFileSync7, existsSync as existsSync11, mkdirSync as mkdirSync9, statSync as statSync6, readdirSync as readdirSync3, rmSync as rmSync4, openSync as openSync3 } from "node:fs";
|
|
6451
|
+
import { join as join14, dirname as dirname6 } from "node:path";
|
|
6335
6452
|
import { createHash as createHash4 } from "node:crypto";
|
|
6336
6453
|
|
|
6337
6454
|
// src/utils/tdd-helpers.js
|
|
@@ -7371,7 +7488,7 @@ function _detectTestFramework() {
|
|
|
7371
7488
|
try {
|
|
7372
7489
|
const root = directory || process.cwd();
|
|
7373
7490
|
const pkgPath = join14(root, "package.json");
|
|
7374
|
-
if (
|
|
7491
|
+
if (existsSync11(pkgPath)) {
|
|
7375
7492
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
7376
7493
|
const testScript = String(pkg?.scripts?.test || "");
|
|
7377
7494
|
const deps = { ...pkg?.devDependencies, ...pkg?.dependencies };
|
|
@@ -7393,9 +7510,9 @@ function _detectTestFramework() {
|
|
|
7393
7510
|
const testDirs = ["src/tests", "tests", "test", "__tests__"];
|
|
7394
7511
|
for (const td of testDirs) {
|
|
7395
7512
|
const dirPath = join14(root, td);
|
|
7396
|
-
if (!
|
|
7513
|
+
if (!existsSync11(dirPath))
|
|
7397
7514
|
continue;
|
|
7398
|
-
const files =
|
|
7515
|
+
const files = readdirSync3(dirPath).filter((f) => /\.test\./.test(f) || /\.spec\./.test(f));
|
|
7399
7516
|
if (files.length > 0) {
|
|
7400
7517
|
const content = readFileSync13(join14(dirPath, files[0]), "utf-8");
|
|
7401
7518
|
if (/from\s+['"]node:test['"]/.test(content)) {
|
|
@@ -7440,7 +7557,7 @@ function _acquireLock(testPath) {
|
|
|
7440
7557
|
if (err.code !== "EEXIST")
|
|
7441
7558
|
return false;
|
|
7442
7559
|
try {
|
|
7443
|
-
const st =
|
|
7560
|
+
const st = statSync6(lockPath);
|
|
7444
7561
|
if (Date.now() - st.mtimeMs >= LOCK_EXPIRE_MS) {
|
|
7445
7562
|
rmSync4(lockPath, { force: true });
|
|
7446
7563
|
try {
|
|
@@ -7467,7 +7584,7 @@ function _releaseLock(testPath) {
|
|
|
7467
7584
|
}
|
|
7468
7585
|
function _isInCooldown(testPath) {
|
|
7469
7586
|
try {
|
|
7470
|
-
if (!
|
|
7587
|
+
if (!existsSync11(ENFORCEMENT_COOLDOWN_FILE2))
|
|
7471
7588
|
return false;
|
|
7472
7589
|
const hash = createHash4("sha256").update(testPath).digest("hex").slice(0, 16);
|
|
7473
7590
|
const lines = readFileSync13(ENFORCEMENT_COOLDOWN_FILE2, "utf-8").trim().split("\n").filter(Boolean);
|
|
@@ -7487,13 +7604,13 @@ function _isInCooldown(testPath) {
|
|
|
7487
7604
|
}
|
|
7488
7605
|
function _recordCooldown(testPath) {
|
|
7489
7606
|
try {
|
|
7490
|
-
mkdirSync9(
|
|
7607
|
+
mkdirSync9(dirname6(ENFORCEMENT_COOLDOWN_FILE2), { recursive: true });
|
|
7491
7608
|
const hash = createHash4("sha256").update(testPath).digest("hex").slice(0, 16);
|
|
7492
7609
|
const entry = JSON.stringify({ h: hash, ts: Date.now() }) + "\n";
|
|
7493
|
-
|
|
7610
|
+
appendFileSync7(ENFORCEMENT_COOLDOWN_FILE2, entry);
|
|
7494
7611
|
const lines = readFileSync13(ENFORCEMENT_COOLDOWN_FILE2, "utf-8").trim().split("\n").filter(Boolean);
|
|
7495
7612
|
if (lines.length > 500) {
|
|
7496
|
-
|
|
7613
|
+
writeFileSync10(ENFORCEMENT_COOLDOWN_FILE2, lines.slice(-200).join("\n") + "\n");
|
|
7497
7614
|
}
|
|
7498
7615
|
} catch {
|
|
7499
7616
|
}
|
|
@@ -7555,13 +7672,13 @@ function buildTestSkeleton(filePath, sourceContent = "", options = {}) {
|
|
|
7555
7672
|
testPath = testPath.replace(new RegExp("\\.[^.]+$"), "." + fw.testExt);
|
|
7556
7673
|
}
|
|
7557
7674
|
const exports = extractExports(sourceContent, extLower);
|
|
7558
|
-
return { path: testPath, content: skeletonFn(name, exports, "full", strict, quality, sourceContent), dir:
|
|
7675
|
+
return { path: testPath, content: skeletonFn(name, exports, "full", strict, quality, sourceContent), dir: dirname6(testPath) };
|
|
7559
7676
|
}
|
|
7560
7677
|
function enforceTestFile(filePath) {
|
|
7561
7678
|
console.error(`[vibeOS] [tdd-enforce] enforceTestFile called for ${filePath}`);
|
|
7562
7679
|
let sourceContent = "";
|
|
7563
7680
|
try {
|
|
7564
|
-
if (
|
|
7681
|
+
if (existsSync11(filePath)) {
|
|
7565
7682
|
sourceContent = readFileSync13(filePath, "utf-8");
|
|
7566
7683
|
}
|
|
7567
7684
|
} catch {
|
|
@@ -7570,7 +7687,7 @@ function enforceTestFile(filePath) {
|
|
|
7570
7687
|
const skeleton = buildTestSkeleton(filePath, sourceContent, { strict: sel.tdd_strict !== false, quality: sel.tdd_quality !== false });
|
|
7571
7688
|
if (!skeleton)
|
|
7572
7689
|
return null;
|
|
7573
|
-
if (
|
|
7690
|
+
if (existsSync11(skeleton.path))
|
|
7574
7691
|
return null;
|
|
7575
7692
|
if (_enforcementCooldown.has(skeleton.path))
|
|
7576
7693
|
return null;
|
|
@@ -7580,7 +7697,7 @@ function enforceTestFile(filePath) {
|
|
|
7580
7697
|
return null;
|
|
7581
7698
|
try {
|
|
7582
7699
|
mkdirSync9(skeleton.dir, { recursive: true });
|
|
7583
|
-
|
|
7700
|
+
writeFileSync10(skeleton.path, skeleton.content);
|
|
7584
7701
|
_enforcementCooldown.add(skeleton.path);
|
|
7585
7702
|
_recordCooldown(skeleton.path);
|
|
7586
7703
|
try {
|
|
@@ -7842,7 +7959,7 @@ var onToolExecuteBefore = async (input, output) => {
|
|
|
7842
7959
|
if (sel.delegation_enforce && currentTier === "high" && args && typeof args === "object") {
|
|
7843
7960
|
const actualArgs = args || output && output.args || {};
|
|
7844
7961
|
const originalPath = actualArgs.filePath || actualArgs.file_path || "";
|
|
7845
|
-
const
|
|
7962
|
+
const basename9 = originalPath.split("/").pop() || "blocked";
|
|
7846
7963
|
const apiResult = await remoteCall("delegateCheck", [tLower, currentTier, currentModel, _prompt], () => ({
|
|
7847
7964
|
blocked: true,
|
|
7848
7965
|
savings: _estEdit
|
|
@@ -7851,7 +7968,7 @@ var onToolExecuteBefore = async (input, output) => {
|
|
|
7851
7968
|
const savings = apiResult?.savings ?? _estEdit;
|
|
7852
7969
|
if (isBlocked) {
|
|
7853
7970
|
if (tLower === "write") {
|
|
7854
|
-
actualArgs.filePath = `/tmp/vibeos-enforcement-blocked-${
|
|
7971
|
+
actualArgs.filePath = `/tmp/vibeos-enforcement-blocked-${basename9}`;
|
|
7855
7972
|
if (actualArgs.file_path !== void 0)
|
|
7856
7973
|
actualArgs.file_path = actualArgs.filePath;
|
|
7857
7974
|
} else if (tLower === "edit" || tLower === "notebookedit") {
|
|
@@ -7884,10 +8001,10 @@ var onToolExecuteBefore = async (input, output) => {
|
|
|
7884
8001
|
}
|
|
7885
8002
|
} else {
|
|
7886
8003
|
const missed = recordMissedContext7(_estC7);
|
|
7887
|
-
if (!
|
|
8004
|
+
if (!existsSync12(CONTEXT7_INSTALL_FLAG)) {
|
|
7888
8005
|
try {
|
|
7889
|
-
mkdirSync10(
|
|
7890
|
-
|
|
8006
|
+
mkdirSync10(dirname7(CONTEXT7_INSTALL_FLAG), { recursive: true });
|
|
8007
|
+
writeFileSync11(CONTEXT7_INSTALL_FLAG, "");
|
|
7891
8008
|
} catch {
|
|
7892
8009
|
}
|
|
7893
8010
|
console.error(`[vibeOS] \u{1F4A1} Install context7 MCP to save ~$0.06/turn on docs: \`claude mcp add context7 npx @upstash/context7-mcp\``);
|
|
@@ -7910,8 +8027,6 @@ var onToolExecuteBefore = async (input, output) => {
|
|
|
7910
8027
|
}
|
|
7911
8028
|
};
|
|
7912
8029
|
var onToolExecuteAfter = async (input, output) => {
|
|
7913
|
-
if (!loadSelection().enabled)
|
|
7914
|
-
return;
|
|
7915
8030
|
_refreshModel(projectDirectory);
|
|
7916
8031
|
let _footerText = "";
|
|
7917
8032
|
try {
|
|
@@ -8031,7 +8146,7 @@ var onToolExecuteAfter = async (input, output) => {
|
|
|
8031
8146
|
if (t === "task") {
|
|
8032
8147
|
const quality = scoreTaskQuality(output?.result || output?.text || "", input?.args?.prompt || "");
|
|
8033
8148
|
try {
|
|
8034
|
-
|
|
8149
|
+
appendFileSync8(SAVINGS_LEDGER_FILE, JSON.stringify({
|
|
8035
8150
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8036
8151
|
kind: "quality",
|
|
8037
8152
|
score: quality,
|
|
@@ -8172,7 +8287,7 @@ ${pendingUiNote}`;
|
|
|
8172
8287
|
if (guardRe.test(fp3)) {
|
|
8173
8288
|
const guardIcons = { flag: "!", warn: "!!", hint: "_" };
|
|
8174
8289
|
const guardIcon = guardIcons.flag || "!";
|
|
8175
|
-
const fn =
|
|
8290
|
+
const fn = basename7(fp3);
|
|
8176
8291
|
console.error(`[flow-enforcer] ${guardIcon} [guard] ${fn}: protected project doc modified \u2014 verify user intent`);
|
|
8177
8292
|
}
|
|
8178
8293
|
}
|
|
@@ -8250,7 +8365,7 @@ ${pendingUiNote}`;
|
|
|
8250
8365
|
};
|
|
8251
8366
|
|
|
8252
8367
|
// src/lib/hooks/session-compact.js
|
|
8253
|
-
import { readFileSync as readFileSync14, existsSync as
|
|
8368
|
+
import { readFileSync as readFileSync14, existsSync as existsSync13 } from "node:fs";
|
|
8254
8369
|
var onSessionCompacting = async (_input, output) => {
|
|
8255
8370
|
if (!loadSelection().enabled)
|
|
8256
8371
|
return;
|
|
@@ -8259,7 +8374,7 @@ var onSessionCompacting = async (_input, output) => {
|
|
|
8259
8374
|
const needsCompact = turnCount >= 7;
|
|
8260
8375
|
const indexPath = getSessionIndexPath();
|
|
8261
8376
|
let recent = "";
|
|
8262
|
-
if (
|
|
8377
|
+
if (existsSync13(indexPath)) {
|
|
8263
8378
|
try {
|
|
8264
8379
|
const lines = readFileSync14(indexPath, "utf-8").trim().split("\n").slice(-30);
|
|
8265
8380
|
recent = lines.map((l) => {
|
|
@@ -8306,6 +8421,8 @@ var setShellDirectory = (dir) => {
|
|
|
8306
8421
|
var onShellEnv = async (_input, output) => {
|
|
8307
8422
|
try {
|
|
8308
8423
|
_refreshModel(directory2 || process.cwd());
|
|
8424
|
+
if (!output)
|
|
8425
|
+
output = {};
|
|
8309
8426
|
output.env ??= {};
|
|
8310
8427
|
output.env.OPENCODE_MODEL_TIER = currentTier || "unknown";
|
|
8311
8428
|
output.env.OPENCODE_MODEL = currentModel || "unknown";
|
|
@@ -8330,8 +8447,8 @@ function _loadOpenCodeProviders() {
|
|
|
8330
8447
|
function _readOpenCodeConfigObject(dir) {
|
|
8331
8448
|
const jsonPath = join15(dir, "opencode.json");
|
|
8332
8449
|
const jsoncPath = join15(dir, "opencode.jsonc");
|
|
8333
|
-
if (
|
|
8334
|
-
if (
|
|
8450
|
+
if (existsSync14(jsonPath)) return safeJsonParse3(readFileSync15(jsonPath, "utf-8"));
|
|
8451
|
+
if (existsSync14(jsoncPath)) return _parseJsonc(readFileSync15(jsoncPath, "utf-8"));
|
|
8335
8452
|
return {};
|
|
8336
8453
|
}
|
|
8337
8454
|
function _parseJsonc(raw) {
|
|
@@ -8356,11 +8473,11 @@ function _modelTier2(id2) {
|
|
|
8356
8473
|
}
|
|
8357
8474
|
function backupFile(path, label) {
|
|
8358
8475
|
try {
|
|
8359
|
-
if (!
|
|
8476
|
+
if (!existsSync14(path)) return null;
|
|
8360
8477
|
const bkDir = join15(USER_HOME2, ".claude", ".backups");
|
|
8361
8478
|
mkdirSync11(bkDir, { recursive: true });
|
|
8362
|
-
const bk = join15(bkDir, `${
|
|
8363
|
-
|
|
8479
|
+
const bk = join15(bkDir, `${basename8(path)}.${label}.${Date.now()}.bak`);
|
|
8480
|
+
copyFileSync5(path, bk);
|
|
8364
8481
|
return bk;
|
|
8365
8482
|
} catch {
|
|
8366
8483
|
return null;
|
|
@@ -8382,7 +8499,7 @@ function loadMcpPort() {
|
|
|
8382
8499
|
return n;
|
|
8383
8500
|
}
|
|
8384
8501
|
try {
|
|
8385
|
-
if (
|
|
8502
|
+
if (existsSync14(TIERS_FILE2)) {
|
|
8386
8503
|
const tiers = safeJsonParse3(readFileSync15(TIERS_FILE2, "utf-8"));
|
|
8387
8504
|
const cfg = tiers?.selection?.mcp_port ?? tiers?.mcp_port;
|
|
8388
8505
|
if (cfg === false || cfg === "disabled" || cfg === 0) return 0;
|
|
@@ -8395,14 +8512,14 @@ function loadMcpPort() {
|
|
|
8395
8512
|
}
|
|
8396
8513
|
function persistMcpPort(port) {
|
|
8397
8514
|
try {
|
|
8398
|
-
if (!
|
|
8515
|
+
if (!existsSync14(TIERS_FILE2)) return;
|
|
8399
8516
|
const tiers = safeJsonParse3(readFileSync15(TIERS_FILE2, "utf-8"));
|
|
8400
8517
|
tiers.selection ??= {};
|
|
8401
8518
|
if (Number(tiers.selection.mcp_port) === Number(port)) return;
|
|
8402
8519
|
tiers.selection.mcp_port = port;
|
|
8403
|
-
mkdirSync11(
|
|
8520
|
+
mkdirSync11(dirname8(TIERS_FILE2), { recursive: true });
|
|
8404
8521
|
const tmp = TIERS_FILE2 + ".tmp." + Date.now();
|
|
8405
|
-
|
|
8522
|
+
writeFileSync12(tmp, JSON.stringify(tiers, null, 2) + "\n", "utf-8");
|
|
8406
8523
|
renameSync6(tmp, TIERS_FILE2);
|
|
8407
8524
|
} catch {
|
|
8408
8525
|
}
|
|
@@ -8592,12 +8709,12 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8592
8709
|
} else {
|
|
8593
8710
|
console.error("[vibeOS] NO MODEL \u2014 enforcement disabled, will auto-detect on first hook");
|
|
8594
8711
|
}
|
|
8595
|
-
console.error(`[vibeOS] auto-config guard: currentModel=${currentModel ? "SET" : "NONE"}, TIERS_FILE=${TIERS_FILE2}, exists=${
|
|
8596
|
-
if (currentModel || !
|
|
8712
|
+
console.error(`[vibeOS] auto-config guard: currentModel=${currentModel ? "SET" : "NONE"}, TIERS_FILE=${TIERS_FILE2}, exists=${existsSync14(TIERS_FILE2)}`);
|
|
8713
|
+
if (currentModel || !existsSync14(TIERS_FILE2)) {
|
|
8597
8714
|
try {
|
|
8598
8715
|
let _tiersData2;
|
|
8599
8716
|
let _wasCorrupted = false;
|
|
8600
|
-
if (
|
|
8717
|
+
if (existsSync14(TIERS_FILE2)) {
|
|
8601
8718
|
try {
|
|
8602
8719
|
_tiersData2 = safeJsonParse3(readFileSync15(TIERS_FILE2, "utf-8"));
|
|
8603
8720
|
} catch {
|
|
@@ -8670,9 +8787,9 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8670
8787
|
if (_tiersData2) {
|
|
8671
8788
|
_tiersData2.selection ??= {};
|
|
8672
8789
|
if (_tiersData2.selection.mcp_port === void 0) _tiersData2.selection.mcp_port = 9578;
|
|
8673
|
-
mkdirSync11(
|
|
8790
|
+
mkdirSync11(dirname8(TIERS_FILE2), { recursive: true });
|
|
8674
8791
|
const _tmp = TIERS_FILE2 + ".tmp." + Date.now();
|
|
8675
|
-
|
|
8792
|
+
writeFileSync12(_tmp, JSON.stringify(_tiersData2, null, 2) + "\n", "utf-8");
|
|
8676
8793
|
renameSync6(_tmp, TIERS_FILE2);
|
|
8677
8794
|
console.error(`[vibeOS] auto-synced model-tiers.json: brain=${_brain.id} medium=${_tiersData2.trinity?.medium?.oc || ""} cheap=${_tiersData2.trinity?.cheap?.oc || ""}`);
|
|
8678
8795
|
const _tiersCfg = safeJsonParse3(readFileSync15(TIERS_FILE2, "utf-8"));
|
|
@@ -8696,7 +8813,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8696
8813
|
if (_mt.selection && (_mt.selection.mcp_port === void 0 || _mt.selection.mcp_port === null)) {
|
|
8697
8814
|
_mt.selection.mcp_port = 9578;
|
|
8698
8815
|
const _tmp = TIERS_FILE2 + ".tmp." + Date.now();
|
|
8699
|
-
|
|
8816
|
+
writeFileSync12(_tmp, JSON.stringify(_mt, null, 2) + "\n", "utf-8");
|
|
8700
8817
|
renameSync6(_tmp, TIERS_FILE2);
|
|
8701
8818
|
}
|
|
8702
8819
|
} catch {
|
|
@@ -8716,7 +8833,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8716
8833
|
console.error(`[vibeOS] project-memory init failed for ${fp}: ${err.message}`);
|
|
8717
8834
|
}
|
|
8718
8835
|
try {
|
|
8719
|
-
if (directory3 &&
|
|
8836
|
+
if (directory3 && existsSync14(directory3)) {
|
|
8720
8837
|
const techStack = detectTechStack(directory3);
|
|
8721
8838
|
const result = ensureProjectDocs(directory3, techStack);
|
|
8722
8839
|
if (result.created.length > 0) console.error(`[vibeOS] Project Guard: created ${result.created.join(", ")}`);
|
|
@@ -8728,6 +8845,10 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8728
8845
|
} catch (err) {
|
|
8729
8846
|
console.error(`[vibeOS] Project Guard init failed: ${err.message}`);
|
|
8730
8847
|
}
|
|
8848
|
+
try {
|
|
8849
|
+
writeSelection("enabled", true);
|
|
8850
|
+
} catch {
|
|
8851
|
+
}
|
|
8731
8852
|
const _tiersData = (() => {
|
|
8732
8853
|
try {
|
|
8733
8854
|
return safeJsonParse3(readFileSync15(TIERS_FILE2, "utf-8"));
|
|
@@ -8754,8 +8875,8 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8754
8875
|
directory: directory3,
|
|
8755
8876
|
safeJsonParse: safeJsonParse3,
|
|
8756
8877
|
readFileSync: readFileSync15,
|
|
8757
|
-
writeFileSync:
|
|
8758
|
-
existsSync:
|
|
8878
|
+
writeFileSync: writeFileSync12,
|
|
8879
|
+
existsSync: existsSync14,
|
|
8759
8880
|
renameSync: renameSync6,
|
|
8760
8881
|
TIERS_FILE: TIERS_FILE2,
|
|
8761
8882
|
USER_HOME: USER_HOME2,
|
|
@@ -8825,12 +8946,6 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8825
8946
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
8826
8947
|
return onMessagesTransform(_input, output);
|
|
8827
8948
|
},
|
|
8828
|
-
"experimental.text.complete": async (input, output) => {
|
|
8829
|
-
await _appendFooter(input, output, directory3);
|
|
8830
|
-
},
|
|
8831
|
-
"message.updated": async (input, output) => {
|
|
8832
|
-
await _appendFooter(input, output, directory3);
|
|
8833
|
-
},
|
|
8834
8949
|
"experimental.session.compacting": async (_input, output) => {
|
|
8835
8950
|
return onSessionCompacting(_input, output);
|
|
8836
8951
|
},
|
|
@@ -8841,6 +8956,12 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8841
8956
|
if (typeof setShellDirectory === "function") setShellDirectory(directory3 || "");
|
|
8842
8957
|
return onShellEnv(_input, output);
|
|
8843
8958
|
},
|
|
8959
|
+
"experimental.text.complete": async (_input, output) => {
|
|
8960
|
+
await _appendFooter(_input, output, directory3);
|
|
8961
|
+
},
|
|
8962
|
+
"message.updated": async (_input, output) => {
|
|
8963
|
+
await _appendFooter(_input, output, directory3);
|
|
8964
|
+
},
|
|
8844
8965
|
tool: {
|
|
8845
8966
|
trinity: tool(createTrinityTool(trinityDeps)),
|
|
8846
8967
|
"research-audit": tool({
|
|
@@ -8954,7 +9075,7 @@ ${report.narrative}`);
|
|
|
8954
9075
|
getSessionMetrics: () => computeSessionMetrics(readFullState(), _OC_SID),
|
|
8955
9076
|
getTodos: () => loadTodos(),
|
|
8956
9077
|
listReports: (filter) => {
|
|
8957
|
-
if (!
|
|
9078
|
+
if (!existsSync14(REPORTS_DIR)) {
|
|
8958
9079
|
const e = new Error("reports dir not found");
|
|
8959
9080
|
e.status = 404;
|
|
8960
9081
|
throw e;
|