vibeostheog 0.15.34 → 0.16.0
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 +15 -0
- package/README.md +40 -1
- package/package.json +1 -1
- package/src/index.js +149 -126
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## 0.16.0
|
|
2
|
+
- feat: dopamine-style footer + natural language system directives
|
|
3
|
+
- feat: dopamine-style footer + natural language system directives
|
|
4
|
+
- feat: turn-aware compaction directive at turn 7+
|
|
5
|
+
- feat: add forensic/web-research modes + 1084-datapoint benchmark
|
|
6
|
+
- fix: flash icon only when API connected, unified [VIBE→MODE⚡] format
|
|
7
|
+
- docs: Security section + Context7 cost optimization docs
|
|
8
|
+
- docs: add Security section with API token emphasis and Context7 cost optimization docs
|
|
9
|
+
- docs: persist all benchmark data + compaction research
|
|
10
|
+
- docs: reformat README as user-facing PM doc, move internals to AGENTS.md, cleanup .gitignore
|
|
11
|
+
Merge pull request #35 from DrunkkToys/refactor/simplify-chat-transform
|
|
12
|
+
readme: center VIBE autoswitching as the core value proposition
|
|
13
|
+
Revert "fix: add system prompt cache savings tracking"
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
## 0.15.23
|
|
2
17
|
- feat: add trinity api-token command to inject VIBEOS_API_TOKEN
|
|
3
18
|
|
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.16.0",
|
|
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 mkdirSync10, 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,
|
|
@@ -5126,7 +5126,7 @@ async function probeModel(modelId, auth) {
|
|
|
5126
5126
|
}
|
|
5127
5127
|
}
|
|
5128
5128
|
|
|
5129
|
-
// src/lib/hooks/footer.
|
|
5129
|
+
// src/lib/hooks/footer.js
|
|
5130
5130
|
import { readFileSync as readFileSync12 } from "node:fs";
|
|
5131
5131
|
import { join as join13 } from "node:path";
|
|
5132
5132
|
import { homedir as homedir9, tmpdir as tmpdir6 } from "node:os";
|
|
@@ -5603,7 +5603,7 @@ function buildProjectBriefing(directory3) {
|
|
|
5603
5603
|
const label = currentProjectName || (directory3 ? basename6(directory3) : "");
|
|
5604
5604
|
if (!label)
|
|
5605
5605
|
return null;
|
|
5606
|
-
return `
|
|
5606
|
+
return `Working on ${label}. Keep focused on this repository and its conventions.`;
|
|
5607
5607
|
}
|
|
5608
5608
|
function ensureProjectSkill(dir, fp2) {
|
|
5609
5609
|
const skillsDir = join12(dir, ".opencode", "skills");
|
|
@@ -5904,20 +5904,20 @@ var onMessagesTransform = async (_input, output) => {
|
|
|
5904
5904
|
}
|
|
5905
5905
|
};
|
|
5906
5906
|
var C7_URGENCY = {
|
|
5907
|
-
required: "
|
|
5908
|
-
optional: "
|
|
5907
|
+
required: " This turn, context7 usage is required.",
|
|
5908
|
+
optional: " This turn, context7 is optional \u2014 use it if helpful."
|
|
5909
5909
|
};
|
|
5910
5910
|
function context7Directive(cv) {
|
|
5911
5911
|
const urgency = cv?.context7_urgency || "preferred";
|
|
5912
|
-
return "
|
|
5912
|
+
return "When looking up library or framework documentation (docs.*, readthedocs.*, npmjs.com/package/*, pypi.org/project/*, pkg.go.dev, /api/reference/), use mcp__context7__resolve-library-id and mcp__context7__get-library-docs if they are available instead of WebFetch or WebSearch \u2014 they cost less. Saves roughly $0.06 per turn on average." + (C7_URGENCY[urgency] || "");
|
|
5913
5913
|
}
|
|
5914
5914
|
function thinkingDirective(level) {
|
|
5915
5915
|
const credit = loadCredit();
|
|
5916
5916
|
const creditNote = `credit ${credit}%`;
|
|
5917
5917
|
if (level === "brief") {
|
|
5918
|
-
return `
|
|
5918
|
+
return `You're in brief reasoning mode (${creditNote}). Use extended thinking only for genuinely complex multi-step problems. Keep reasoning concise and skip exploratory scratch work.`;
|
|
5919
5919
|
}
|
|
5920
|
-
return `
|
|
5920
|
+
return `Extended thinking is off (${creditNote}). Respond directly and concisely \u2014 thinking tokens cost money, save them for when the user explicitly asks.`;
|
|
5921
5921
|
}
|
|
5922
5922
|
function orchestratorDirective(cv, sel) {
|
|
5923
5923
|
const tierBias = cv?.tier_bias || "auto";
|
|
@@ -5929,7 +5929,7 @@ function orchestratorDirective(cv, sel) {
|
|
|
5929
5929
|
const cheapModel = TRINITY_CHEAP || "the cheaper model";
|
|
5930
5930
|
const mediumModel = TRINITY_MEDIUM || "the medium model";
|
|
5931
5931
|
const targetModel = tierBias === "cheap" ? cheapModel : tierBias === "medium" ? mediumModel : tierBias === "brain" ? brainModel : `${cheapModel} or ${mediumModel}`;
|
|
5932
|
-
return `
|
|
5932
|
+
return `You coordinate the work. Delegate heavy implementation to Task subagents (runs on ${targetModel}). Your job: verify results, fill gaps, and synthesize. Write/Edit tools are blocked on this tier \u2014 delegate all implementation work. Always show the vibeOS cost footer.` + (tierBias !== "auto" ? ` This turn is biased toward ${tierBias} tier.` : "");
|
|
5933
5933
|
}
|
|
5934
5934
|
var TDD_NOTES = {
|
|
5935
5935
|
lazy: " Skeletons only when explicitly requested.",
|
|
@@ -5939,21 +5939,21 @@ var TDD_NOTES = {
|
|
|
5939
5939
|
function tddDirective(cv, sel) {
|
|
5940
5940
|
const tddMode = cv?.tdd_mode || (sel.tdd_strict ? "strict" : "normal");
|
|
5941
5941
|
const tddFocus = cv?.tdd_focus || [];
|
|
5942
|
-
const focusNote = tddFocus.length > 0 ? ` Focus: ${tddFocus.join(", ")}.` : "";
|
|
5943
|
-
return `
|
|
5942
|
+
const focusNote = tddFocus.length > 0 ? ` Focus on: ${tddFocus.join(", ")}.` : "";
|
|
5943
|
+
return `Auto-create test skeletons for source files you write or edit.${TDD_NOTES[tddMode] || ""}${focusNote} Make sure corresponding test files exist with proper assertions.`;
|
|
5944
5944
|
}
|
|
5945
5945
|
function flowDirective(cv, sel) {
|
|
5946
5946
|
const flowMode = cv?.flow_mode || (sel.flow_enforce ? "normal" : "audit");
|
|
5947
5947
|
const flowFocus = cv?.flow_focus || [];
|
|
5948
|
-
const enforceNote = sel.flow_enforce ? " TODO
|
|
5949
|
-
const focusNote = flowFocus.length > 0 ? ` Focus
|
|
5950
|
-
return `
|
|
5948
|
+
const enforceNote = sel.flow_enforce ? " TODO and FIXME markers are being tracked." : "";
|
|
5949
|
+
const focusNote = flowFocus.length > 0 ? ` Focus on: ${flowFocus.join(", ")}.` : "";
|
|
5950
|
+
return `Follow project conventions when writing or editing code \u2014 check existing patterns and naming conventions.${enforceNote}${focusNote}`;
|
|
5951
5951
|
}
|
|
5952
5952
|
function flowTodosDirective() {
|
|
5953
5953
|
const pendingTodos = loadTodos().filter((t) => t.status === "pending").length;
|
|
5954
5954
|
if (pendingTodos === 0)
|
|
5955
5955
|
return null;
|
|
5956
|
-
return
|
|
5956
|
+
return pendingTodos + " extracted TODO or FIXME items are pending. Consider using `todowrite` to add them to the task list.";
|
|
5957
5957
|
}
|
|
5958
5958
|
function patternDirective(fp2) {
|
|
5959
5959
|
const patterns = promotedProjectPatterns(fp2);
|
|
@@ -5966,11 +5966,11 @@ function patternDirective(fp2) {
|
|
|
5966
5966
|
parts.push("Routines: " + routines.map((r) => r.summary).join("; "));
|
|
5967
5967
|
}
|
|
5968
5968
|
if (frictions.length > 0) {
|
|
5969
|
-
parts.push("
|
|
5969
|
+
parts.push("Things to watch: " + frictions.map((f) => f.summary).join("; "));
|
|
5970
5970
|
}
|
|
5971
5971
|
if (parts.length === 0)
|
|
5972
5972
|
return null;
|
|
5973
|
-
return "
|
|
5973
|
+
return "Learned patterns for this project \u2014 " + parts.join(". ") + ".";
|
|
5974
5974
|
}
|
|
5975
5975
|
function welcomeDirective() {
|
|
5976
5976
|
const sel = loadSelection();
|
|
@@ -5981,14 +5981,13 @@ function welcomeDirective() {
|
|
|
5981
5981
|
}
|
|
5982
5982
|
const active = sel.active_slot || "medium";
|
|
5983
5983
|
const current = currentModel || "(unknown)";
|
|
5984
|
-
return "
|
|
5984
|
+
return "vibeOS is active. Slot: " + active + " (" + current + "). Use `trinity` to switch slots, rebuild, or check status. Run `trinity help` for all commands.";
|
|
5985
5985
|
}
|
|
5986
5986
|
function contextBudgetDirective(_input, output) {
|
|
5987
5987
|
const ctxBudget = estimateContextBudget(_input, output);
|
|
5988
5988
|
if (!ctxBudget || ctxBudget.pct <= 70)
|
|
5989
5989
|
return null;
|
|
5990
|
-
|
|
5991
|
-
return `[context budget: ${severity}] Context window is ${ctxBudget.pct}% full (~${ctxBudget.estimatedTokens} tokens). Consider using Task subagents for heavy work, compressing tool outputs, or starting a new session to avoid context overflow.`;
|
|
5990
|
+
return `Context is ${ctxBudget.pct}% full (~${ctxBudget.estimatedTokens} tokens). Consider delegating heavy work to Task subagents, compressing tool outputs, or starting a new session.`;
|
|
5992
5991
|
}
|
|
5993
5992
|
var onSystemTransform = async (_input, output) => {
|
|
5994
5993
|
if (!loadSelection().enabled)
|
|
@@ -6025,9 +6024,9 @@ var onSystemTransform = async (_input, output) => {
|
|
|
6025
6024
|
pushSystem(output, thinkingDirective(sel.thinking_level));
|
|
6026
6025
|
}
|
|
6027
6026
|
if (stressScore > 0.7) {
|
|
6028
|
-
pushSystem(output, "
|
|
6027
|
+
pushSystem(output, "The user seems quite stressed. Stay calm, structured, and thorough. Use clear markdown with code blocks, lists, and organized sections \u2014 do not mirror their tone. This is important.");
|
|
6029
6028
|
} else if (stressScore > 0.4) {
|
|
6030
|
-
pushSystem(output, "
|
|
6029
|
+
pushSystem(output, "The user seems a bit stressed. Keep responses well-structured with clear markdown and organized sections.");
|
|
6031
6030
|
}
|
|
6032
6031
|
if (_controlVector?.directives?.length > 0) {
|
|
6033
6032
|
for (const directive of _controlVector.directives) {
|
|
@@ -6035,25 +6034,24 @@ var onSystemTransform = async (_input, output) => {
|
|
|
6035
6034
|
}
|
|
6036
6035
|
} else if (_blackboxEnabled && _latestBlackboxState3?.n_interactions > 0) {
|
|
6037
6036
|
const res = _latestBlackboxState3;
|
|
6038
|
-
pushSystem(output, `
|
|
6037
|
+
pushSystem(output, `Current resolution: ${res.resolution || "unresolved"} (${res.sub_regime || "EXPLORING"}). Momentum: ${(res.momentum || 0) > 0 ? "positive" : (res.momentum || 0) < 0 ? "negative" : "neutral"}. If the conversation is looping or stuck, suggest stepping back. If you're converging or closing, push toward a decision.`);
|
|
6039
6038
|
if (res.is_looping && res.loop_intervention_level && res.loop_intervention_level !== "none") {
|
|
6040
|
-
|
|
6041
|
-
pushSystem(output, `[loop prevention: ${severity}] ${_latestBlackboxLoopMsg2 || "The conversation may be looping -- try a different approach."} (level: ${res.loop_intervention_level})`);
|
|
6039
|
+
pushSystem(output, `${_latestBlackboxLoopMsg2 || "The conversation may be circling \u2014 try a fresh angle."} (level: ${res.loop_intervention_level})`);
|
|
6042
6040
|
}
|
|
6043
6041
|
if (res.pivot_detected && _latestBlackboxPivotMsg2) {
|
|
6044
|
-
pushSystem(output, `
|
|
6042
|
+
pushSystem(output, `Topic seems to have shifted: ${_latestBlackboxPivotMsg2}`);
|
|
6045
6043
|
}
|
|
6046
6044
|
}
|
|
6047
6045
|
const projectJob = getActiveJobForProject();
|
|
6048
6046
|
if (latestUserIntent && projectJob && isLikelyOffTopic(latestUserIntent, projectJob)) {
|
|
6049
|
-
pushSystem(output, `
|
|
6047
|
+
pushSystem(output, `There's an active job: "${(projectJob.prompt || "").slice(0, 140)}...". The latest request looks unrelated. Before acting, ask if they want to switch focus.`);
|
|
6050
6048
|
console.error("[vibeOS] [job-focus] off-topic request detected vs active job context");
|
|
6051
6049
|
}
|
|
6052
6050
|
if (sel.delegation_enforce && _controlVector?.enforcement_mode !== "relaxed" && _controlVector?.agent_mode !== "plan") {
|
|
6053
6051
|
pushSystem(output, orchestratorDirective(_controlVector, sel));
|
|
6054
6052
|
}
|
|
6055
6053
|
if (_controlVector?.enforcement_mode !== "relaxed" && _controlVector?.agent_mode !== "plan") {
|
|
6056
|
-
pushSystem(output, "
|
|
6054
|
+
pushSystem(output, "When you have multiple independent tasks, run them all in parallel \u2014 it's faster and cheaper. Only sequence them when one depends on another's output.");
|
|
6057
6055
|
}
|
|
6058
6056
|
if (sel.tdd_enforce && _controlVector?.tdd_mode !== "lazy") {
|
|
6059
6057
|
pushSystem(output, tddDirective(_controlVector, sel));
|
|
@@ -6064,7 +6062,7 @@ var onSystemTransform = async (_input, output) => {
|
|
|
6064
6062
|
pushSystem(output, flowTodosDirective());
|
|
6065
6063
|
}
|
|
6066
6064
|
}
|
|
6067
|
-
pushSystem(output, "
|
|
6065
|
+
pushSystem(output, "AGENTS.md and README.md are protected files \u2014 never edit them without asking. When you add new features, update README.md to document them. AGENTS.md defines the project rules \u2014 follow them.");
|
|
6068
6066
|
pushSystem(output, contextBudgetDirective(_input, output));
|
|
6069
6067
|
if (!oneShot(fp2)) {
|
|
6070
6068
|
pushSystem(output, buildProjectBriefing(currentProjectName || ""));
|
|
@@ -6083,13 +6081,14 @@ var onSystemTransform = async (_input, output) => {
|
|
|
6083
6081
|
}
|
|
6084
6082
|
};
|
|
6085
6083
|
|
|
6086
|
-
// src/lib/hooks/footer.
|
|
6084
|
+
// src/lib/hooks/footer.js
|
|
6087
6085
|
var _cachedAutoMode = null;
|
|
6088
6086
|
var _cachedAutoModeTs = 0;
|
|
6089
6087
|
var AUTO_CACHE_TTL = 6e4;
|
|
6090
6088
|
async function apiAutoSelectMode(regime, stress) {
|
|
6091
6089
|
const now = Date.now();
|
|
6092
|
-
if (_cachedAutoMode && now - _cachedAutoModeTs < AUTO_CACHE_TTL)
|
|
6090
|
+
if (_cachedAutoMode && now - _cachedAutoModeTs < AUTO_CACHE_TTL)
|
|
6091
|
+
return _cachedAutoMode;
|
|
6093
6092
|
try {
|
|
6094
6093
|
const res = await remoteCall("blackboxSelectMode", [regime, stress], null);
|
|
6095
6094
|
if (res?.mode) {
|
|
@@ -6149,24 +6148,35 @@ function readLifetimeSavings2() {
|
|
|
6149
6148
|
}
|
|
6150
6149
|
var _OC_SID5 = "opencode-" + (process.pid || "x") + "-" + Date.now();
|
|
6151
6150
|
function scoreTaskQuality(outputText, promptText) {
|
|
6152
|
-
if (typeof outputText !== "string" || outputText.length === 0)
|
|
6153
|
-
|
|
6151
|
+
if (typeof outputText !== "string" || outputText.length === 0)
|
|
6152
|
+
return 0;
|
|
6153
|
+
if (typeof promptText !== "string")
|
|
6154
|
+
promptText = "";
|
|
6154
6155
|
let score = 50;
|
|
6155
|
-
if (promptText.length > 0 && outputText.length > promptText.length * 0.5)
|
|
6156
|
-
|
|
6157
|
-
if (
|
|
6158
|
-
|
|
6156
|
+
if (promptText.length > 0 && outputText.length > promptText.length * 0.5)
|
|
6157
|
+
score += 10;
|
|
6158
|
+
if (outputText.length < 50)
|
|
6159
|
+
score -= 20;
|
|
6160
|
+
if (/error|failed|unable|cannot|could not/i.test(outputText))
|
|
6161
|
+
score -= 10;
|
|
6162
|
+
if (/TODO|FIXME|placeholder/i.test(outputText) && outputText.length < 200)
|
|
6163
|
+
score -= 15;
|
|
6159
6164
|
const codeBlocks = (outputText.match(/```/g) || []).length;
|
|
6160
|
-
if (codeBlocks >= 2)
|
|
6161
|
-
|
|
6162
|
-
if (outputText.length >
|
|
6165
|
+
if (codeBlocks >= 2)
|
|
6166
|
+
score += 10;
|
|
6167
|
+
if (outputText.length > 500)
|
|
6168
|
+
score += 10;
|
|
6169
|
+
if (outputText.length > 1e3)
|
|
6170
|
+
score += 5;
|
|
6163
6171
|
return Math.max(0, Math.min(100, score));
|
|
6164
6172
|
}
|
|
6165
6173
|
async function _appendFooter(input, output, directory3) {
|
|
6166
|
-
if (!loadSelection3().enabled)
|
|
6174
|
+
if (!loadSelection3().enabled)
|
|
6175
|
+
return;
|
|
6167
6176
|
_refreshModel(directory3);
|
|
6168
6177
|
let _footerStress = 0;
|
|
6169
|
-
if (latestUserIntent)
|
|
6178
|
+
if (latestUserIntent)
|
|
6179
|
+
_footerStress = scoreStress(latestUserIntent);
|
|
6170
6180
|
if (!currentModel) {
|
|
6171
6181
|
try {
|
|
6172
6182
|
const cfg = await client.config.get("model");
|
|
@@ -6180,10 +6190,12 @@ async function _appendFooter(input, output, directory3) {
|
|
|
6180
6190
|
}
|
|
6181
6191
|
try {
|
|
6182
6192
|
const messageID = input?.messageID || input?.messageId || input?.message?.id || output?.messageID || output?.messageId || output?.message?.id || null;
|
|
6183
|
-
if (messageID && textCompletePainted.has(messageID))
|
|
6193
|
+
if (messageID && textCompletePainted.has(messageID))
|
|
6194
|
+
return;
|
|
6184
6195
|
const text = typeof output?.text === "string" ? output.text : typeof output?.result === "string" ? output.result : typeof output?.content === "string" ? output.content : "";
|
|
6185
6196
|
if (!text || text.length < 50) {
|
|
6186
|
-
if (messageID)
|
|
6197
|
+
if (messageID)
|
|
6198
|
+
textCompletePainted.add(messageID);
|
|
6187
6199
|
return;
|
|
6188
6200
|
}
|
|
6189
6201
|
const { ltTasks, ltCache, ltCost, count, sesTasks, sesEdit, sesCredit, sesC7, sesQuota, sesCache, sesTaskDelegations, sesDuration, sesRatePerHour, sesTrend, sesToolBreakdown, sesModelTurns, quality_avg } = readLifetimeSavings2();
|
|
@@ -6233,12 +6245,17 @@ async function _appendFooter(input, output, directory3) {
|
|
|
6233
6245
|
if (bbMode === "relaxed") {
|
|
6234
6246
|
enfTagsFooter.push("[Q&A]");
|
|
6235
6247
|
} else {
|
|
6236
|
-
if (selNowFooter.delegation_enforce)
|
|
6237
|
-
|
|
6238
|
-
if (selNowFooter.
|
|
6239
|
-
|
|
6248
|
+
if (selNowFooter.delegation_enforce)
|
|
6249
|
+
enfTagsFooter.push("[ENF ON]");
|
|
6250
|
+
if (selNowFooter.flow_enforce)
|
|
6251
|
+
enfTagsFooter.push("[FLOW ON]");
|
|
6252
|
+
if (selNowFooter.tdd_enforce)
|
|
6253
|
+
enfTagsFooter.push("[TDD ON]");
|
|
6254
|
+
if (bbMode === "strict")
|
|
6255
|
+
enfTagsFooter.push("[STRICT]");
|
|
6240
6256
|
}
|
|
6241
|
-
if (_modelLocked)
|
|
6257
|
+
if (_modelLocked)
|
|
6258
|
+
enfTagsFooter.push("[LOCK ON]");
|
|
6242
6259
|
let enfSuffixFooter = enfTagsFooter.length > 0 ? ` ${enfTagsFooter.join(" ")}` : "";
|
|
6243
6260
|
if (quality_avg > 0) {
|
|
6244
6261
|
enfSuffixFooter = ` QA:${Math.round(quality_avg)}% ${enfTagsFooter.join(" ")}`;
|
|
@@ -6268,33 +6285,34 @@ async function _appendFooter(input, output, directory3) {
|
|
|
6268
6285
|
} else {
|
|
6269
6286
|
optTagFooter = `[VIBE\u2192${(optModeFooter || "").toUpperCase()}${flashIcon}]`;
|
|
6270
6287
|
}
|
|
6271
|
-
modelTag = `${modelTag}${optTagFooter}${enfSuffixFooter || ""}`;
|
|
6272
6288
|
const stripped = text.replace(/\n\n— .+(?: —)?$/, "");
|
|
6273
|
-
if (stripped !== text)
|
|
6289
|
+
if (stripped !== text)
|
|
6290
|
+
return;
|
|
6274
6291
|
const ltTotal = ltTasks + ltCache;
|
|
6275
|
-
const
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6292
|
+
const modeVerbMap = {
|
|
6293
|
+
balanced: "routing",
|
|
6294
|
+
budget: "saving",
|
|
6295
|
+
quality: "focusing",
|
|
6296
|
+
speed: "moving",
|
|
6297
|
+
longrun: "pacing",
|
|
6298
|
+
auto: "vibing",
|
|
6299
|
+
"web-research": "researching",
|
|
6300
|
+
forensic: "investigating"
|
|
6301
|
+
};
|
|
6302
|
+
const optMode = (optModeFooter || "balanced").toLowerCase();
|
|
6303
|
+
const modeVerb = modeVerbMap[optMode] || "vibing";
|
|
6304
|
+
let vibeLine = `\u2014 ${modeVerb} on ${shortModelName(brainModel)}`;
|
|
6280
6305
|
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`;
|
|
6306
|
+
vibeLine += ` \u2728 $${formatUsd(ltTotal)} saved`;
|
|
6307
|
+
}
|
|
6308
|
+
vibeLine += `, VIBE${flashIcon ? " \u26A1" : ""}`;
|
|
6309
|
+
if (_footerStress > 0.4) {
|
|
6310
|
+
const stressLabel = _footerStress > 0.7 ? "elevated" : "uneven";
|
|
6311
|
+
vibeLine += ` \xB7 ${stressLabel}`;
|
|
6297
6312
|
}
|
|
6313
|
+
const footerText = stripped + `
|
|
6314
|
+
|
|
6315
|
+
${vibeLine} \u2014`;
|
|
6298
6316
|
if (_blackboxEnabled) {
|
|
6299
6317
|
try {
|
|
6300
6318
|
const prevText = _prevOutputText;
|
|
@@ -6310,14 +6328,19 @@ async function _appendFooter(input, output, directory3) {
|
|
|
6310
6328
|
} catch {
|
|
6311
6329
|
}
|
|
6312
6330
|
}
|
|
6313
|
-
if (typeof output?.text === "string")
|
|
6314
|
-
|
|
6315
|
-
else if (typeof output?.
|
|
6316
|
-
|
|
6331
|
+
if (typeof output?.text === "string")
|
|
6332
|
+
output.text = footerText;
|
|
6333
|
+
else if (typeof output?.result === "string")
|
|
6334
|
+
output.result = footerText;
|
|
6335
|
+
else if (typeof output?.content === "string")
|
|
6336
|
+
output.content = footerText;
|
|
6337
|
+
else
|
|
6338
|
+
output.text = footerText;
|
|
6317
6339
|
textCompletePainted.add(messageID);
|
|
6318
6340
|
if (textCompletePainted.size > 500) {
|
|
6319
6341
|
const it = textCompletePainted.values();
|
|
6320
|
-
for (let i = 0; i < 100; i++)
|
|
6342
|
+
for (let i = 0; i < 100; i++)
|
|
6343
|
+
textCompletePainted.delete(it.next().value);
|
|
6321
6344
|
}
|
|
6322
6345
|
} catch (err) {
|
|
6323
6346
|
console.error(`[vibeOS] footer failed: ${err.message}`);
|
|
@@ -6325,13 +6348,13 @@ async function _appendFooter(input, output, directory3) {
|
|
|
6325
6348
|
}
|
|
6326
6349
|
|
|
6327
6350
|
// src/lib/hooks/tool-execute.js
|
|
6328
|
-
import { writeFileSync as
|
|
6329
|
-
import { dirname as
|
|
6351
|
+
import { writeFileSync as writeFileSync11, appendFileSync as appendFileSync6, existsSync as existsSync12, mkdirSync as mkdirSync9 } from "node:fs";
|
|
6352
|
+
import { dirname as dirname7, basename as basename7 } from "node:path";
|
|
6330
6353
|
init_flow_enforcer();
|
|
6331
6354
|
|
|
6332
6355
|
// src/lib/tdd-enforcer.js
|
|
6333
|
-
import { readFileSync as readFileSync13, writeFileSync as
|
|
6334
|
-
import { join as join14, dirname as
|
|
6356
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, appendFileSync as appendFileSync5, existsSync as existsSync11, mkdirSync as mkdirSync8, statSync as statSync6, readdirSync as readdirSync3, rmSync as rmSync4, openSync as openSync3 } from "node:fs";
|
|
6357
|
+
import { join as join14, dirname as dirname6 } from "node:path";
|
|
6335
6358
|
import { createHash as createHash4 } from "node:crypto";
|
|
6336
6359
|
|
|
6337
6360
|
// src/utils/tdd-helpers.js
|
|
@@ -7371,7 +7394,7 @@ function _detectTestFramework() {
|
|
|
7371
7394
|
try {
|
|
7372
7395
|
const root = directory || process.cwd();
|
|
7373
7396
|
const pkgPath = join14(root, "package.json");
|
|
7374
|
-
if (
|
|
7397
|
+
if (existsSync11(pkgPath)) {
|
|
7375
7398
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
7376
7399
|
const testScript = String(pkg?.scripts?.test || "");
|
|
7377
7400
|
const deps = { ...pkg?.devDependencies, ...pkg?.dependencies };
|
|
@@ -7393,9 +7416,9 @@ function _detectTestFramework() {
|
|
|
7393
7416
|
const testDirs = ["src/tests", "tests", "test", "__tests__"];
|
|
7394
7417
|
for (const td of testDirs) {
|
|
7395
7418
|
const dirPath = join14(root, td);
|
|
7396
|
-
if (!
|
|
7419
|
+
if (!existsSync11(dirPath))
|
|
7397
7420
|
continue;
|
|
7398
|
-
const files =
|
|
7421
|
+
const files = readdirSync3(dirPath).filter((f) => /\.test\./.test(f) || /\.spec\./.test(f));
|
|
7399
7422
|
if (files.length > 0) {
|
|
7400
7423
|
const content = readFileSync13(join14(dirPath, files[0]), "utf-8");
|
|
7401
7424
|
if (/from\s+['"]node:test['"]/.test(content)) {
|
|
@@ -7430,7 +7453,7 @@ var COOLDOWN_MS = 6e4;
|
|
|
7430
7453
|
var _enforcementCooldown = /* @__PURE__ */ new Set();
|
|
7431
7454
|
function _acquireLock(testPath) {
|
|
7432
7455
|
try {
|
|
7433
|
-
|
|
7456
|
+
mkdirSync8(ENFORCEMENT_LOCK_DIR, { recursive: true });
|
|
7434
7457
|
const hash = createHash4("sha256").update(testPath).digest("hex").slice(0, 16);
|
|
7435
7458
|
const lockPath = join14(ENFORCEMENT_LOCK_DIR, `${hash}.lock`);
|
|
7436
7459
|
try {
|
|
@@ -7440,7 +7463,7 @@ function _acquireLock(testPath) {
|
|
|
7440
7463
|
if (err.code !== "EEXIST")
|
|
7441
7464
|
return false;
|
|
7442
7465
|
try {
|
|
7443
|
-
const st =
|
|
7466
|
+
const st = statSync6(lockPath);
|
|
7444
7467
|
if (Date.now() - st.mtimeMs >= LOCK_EXPIRE_MS) {
|
|
7445
7468
|
rmSync4(lockPath, { force: true });
|
|
7446
7469
|
try {
|
|
@@ -7467,7 +7490,7 @@ function _releaseLock(testPath) {
|
|
|
7467
7490
|
}
|
|
7468
7491
|
function _isInCooldown(testPath) {
|
|
7469
7492
|
try {
|
|
7470
|
-
if (!
|
|
7493
|
+
if (!existsSync11(ENFORCEMENT_COOLDOWN_FILE2))
|
|
7471
7494
|
return false;
|
|
7472
7495
|
const hash = createHash4("sha256").update(testPath).digest("hex").slice(0, 16);
|
|
7473
7496
|
const lines = readFileSync13(ENFORCEMENT_COOLDOWN_FILE2, "utf-8").trim().split("\n").filter(Boolean);
|
|
@@ -7487,13 +7510,13 @@ function _isInCooldown(testPath) {
|
|
|
7487
7510
|
}
|
|
7488
7511
|
function _recordCooldown(testPath) {
|
|
7489
7512
|
try {
|
|
7490
|
-
|
|
7513
|
+
mkdirSync8(dirname6(ENFORCEMENT_COOLDOWN_FILE2), { recursive: true });
|
|
7491
7514
|
const hash = createHash4("sha256").update(testPath).digest("hex").slice(0, 16);
|
|
7492
7515
|
const entry = JSON.stringify({ h: hash, ts: Date.now() }) + "\n";
|
|
7493
|
-
|
|
7516
|
+
appendFileSync5(ENFORCEMENT_COOLDOWN_FILE2, entry);
|
|
7494
7517
|
const lines = readFileSync13(ENFORCEMENT_COOLDOWN_FILE2, "utf-8").trim().split("\n").filter(Boolean);
|
|
7495
7518
|
if (lines.length > 500) {
|
|
7496
|
-
|
|
7519
|
+
writeFileSync10(ENFORCEMENT_COOLDOWN_FILE2, lines.slice(-200).join("\n") + "\n");
|
|
7497
7520
|
}
|
|
7498
7521
|
} catch {
|
|
7499
7522
|
}
|
|
@@ -7555,13 +7578,13 @@ function buildTestSkeleton(filePath, sourceContent = "", options = {}) {
|
|
|
7555
7578
|
testPath = testPath.replace(new RegExp("\\.[^.]+$"), "." + fw.testExt);
|
|
7556
7579
|
}
|
|
7557
7580
|
const exports = extractExports(sourceContent, extLower);
|
|
7558
|
-
return { path: testPath, content: skeletonFn(name, exports, "full", strict, quality, sourceContent), dir:
|
|
7581
|
+
return { path: testPath, content: skeletonFn(name, exports, "full", strict, quality, sourceContent), dir: dirname6(testPath) };
|
|
7559
7582
|
}
|
|
7560
7583
|
function enforceTestFile(filePath) {
|
|
7561
7584
|
console.error(`[vibeOS] [tdd-enforce] enforceTestFile called for ${filePath}`);
|
|
7562
7585
|
let sourceContent = "";
|
|
7563
7586
|
try {
|
|
7564
|
-
if (
|
|
7587
|
+
if (existsSync11(filePath)) {
|
|
7565
7588
|
sourceContent = readFileSync13(filePath, "utf-8");
|
|
7566
7589
|
}
|
|
7567
7590
|
} catch {
|
|
@@ -7570,7 +7593,7 @@ function enforceTestFile(filePath) {
|
|
|
7570
7593
|
const skeleton = buildTestSkeleton(filePath, sourceContent, { strict: sel.tdd_strict !== false, quality: sel.tdd_quality !== false });
|
|
7571
7594
|
if (!skeleton)
|
|
7572
7595
|
return null;
|
|
7573
|
-
if (
|
|
7596
|
+
if (existsSync11(skeleton.path))
|
|
7574
7597
|
return null;
|
|
7575
7598
|
if (_enforcementCooldown.has(skeleton.path))
|
|
7576
7599
|
return null;
|
|
@@ -7579,8 +7602,8 @@ function enforceTestFile(filePath) {
|
|
|
7579
7602
|
if (!_acquireLock(skeleton.path))
|
|
7580
7603
|
return null;
|
|
7581
7604
|
try {
|
|
7582
|
-
|
|
7583
|
-
|
|
7605
|
+
mkdirSync8(skeleton.dir, { recursive: true });
|
|
7606
|
+
writeFileSync10(skeleton.path, skeleton.content);
|
|
7584
7607
|
_enforcementCooldown.add(skeleton.path);
|
|
7585
7608
|
_recordCooldown(skeleton.path);
|
|
7586
7609
|
try {
|
|
@@ -7842,7 +7865,7 @@ var onToolExecuteBefore = async (input, output) => {
|
|
|
7842
7865
|
if (sel.delegation_enforce && currentTier === "high" && args && typeof args === "object") {
|
|
7843
7866
|
const actualArgs = args || output && output.args || {};
|
|
7844
7867
|
const originalPath = actualArgs.filePath || actualArgs.file_path || "";
|
|
7845
|
-
const
|
|
7868
|
+
const basename9 = originalPath.split("/").pop() || "blocked";
|
|
7846
7869
|
const apiResult = await remoteCall("delegateCheck", [tLower, currentTier, currentModel, _prompt], () => ({
|
|
7847
7870
|
blocked: true,
|
|
7848
7871
|
savings: _estEdit
|
|
@@ -7851,7 +7874,7 @@ var onToolExecuteBefore = async (input, output) => {
|
|
|
7851
7874
|
const savings = apiResult?.savings ?? _estEdit;
|
|
7852
7875
|
if (isBlocked) {
|
|
7853
7876
|
if (tLower === "write") {
|
|
7854
|
-
actualArgs.filePath = `/tmp/vibeos-enforcement-blocked-${
|
|
7877
|
+
actualArgs.filePath = `/tmp/vibeos-enforcement-blocked-${basename9}`;
|
|
7855
7878
|
if (actualArgs.file_path !== void 0)
|
|
7856
7879
|
actualArgs.file_path = actualArgs.filePath;
|
|
7857
7880
|
} else if (tLower === "edit" || tLower === "notebookedit") {
|
|
@@ -7884,10 +7907,10 @@ var onToolExecuteBefore = async (input, output) => {
|
|
|
7884
7907
|
}
|
|
7885
7908
|
} else {
|
|
7886
7909
|
const missed = recordMissedContext7(_estC7);
|
|
7887
|
-
if (!
|
|
7910
|
+
if (!existsSync12(CONTEXT7_INSTALL_FLAG)) {
|
|
7888
7911
|
try {
|
|
7889
|
-
|
|
7890
|
-
|
|
7912
|
+
mkdirSync9(dirname7(CONTEXT7_INSTALL_FLAG), { recursive: true });
|
|
7913
|
+
writeFileSync11(CONTEXT7_INSTALL_FLAG, "");
|
|
7891
7914
|
} catch {
|
|
7892
7915
|
}
|
|
7893
7916
|
console.error(`[vibeOS] \u{1F4A1} Install context7 MCP to save ~$0.06/turn on docs: \`claude mcp add context7 npx @upstash/context7-mcp\``);
|
|
@@ -8031,7 +8054,7 @@ var onToolExecuteAfter = async (input, output) => {
|
|
|
8031
8054
|
if (t === "task") {
|
|
8032
8055
|
const quality = scoreTaskQuality(output?.result || output?.text || "", input?.args?.prompt || "");
|
|
8033
8056
|
try {
|
|
8034
|
-
|
|
8057
|
+
appendFileSync6(SAVINGS_LEDGER_FILE, JSON.stringify({
|
|
8035
8058
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8036
8059
|
kind: "quality",
|
|
8037
8060
|
score: quality,
|
|
@@ -8172,7 +8195,7 @@ ${pendingUiNote}`;
|
|
|
8172
8195
|
if (guardRe.test(fp3)) {
|
|
8173
8196
|
const guardIcons = { flag: "!", warn: "!!", hint: "_" };
|
|
8174
8197
|
const guardIcon = guardIcons.flag || "!";
|
|
8175
|
-
const fn =
|
|
8198
|
+
const fn = basename7(fp3);
|
|
8176
8199
|
console.error(`[flow-enforcer] ${guardIcon} [guard] ${fn}: protected project doc modified \u2014 verify user intent`);
|
|
8177
8200
|
}
|
|
8178
8201
|
}
|
|
@@ -8250,7 +8273,7 @@ ${pendingUiNote}`;
|
|
|
8250
8273
|
};
|
|
8251
8274
|
|
|
8252
8275
|
// src/lib/hooks/session-compact.js
|
|
8253
|
-
import { readFileSync as readFileSync14, existsSync as
|
|
8276
|
+
import { readFileSync as readFileSync14, existsSync as existsSync13 } from "node:fs";
|
|
8254
8277
|
var onSessionCompacting = async (_input, output) => {
|
|
8255
8278
|
if (!loadSelection().enabled)
|
|
8256
8279
|
return;
|
|
@@ -8259,7 +8282,7 @@ var onSessionCompacting = async (_input, output) => {
|
|
|
8259
8282
|
const needsCompact = turnCount >= 7;
|
|
8260
8283
|
const indexPath = getSessionIndexPath();
|
|
8261
8284
|
let recent = "";
|
|
8262
|
-
if (
|
|
8285
|
+
if (existsSync13(indexPath)) {
|
|
8263
8286
|
try {
|
|
8264
8287
|
const lines = readFileSync14(indexPath, "utf-8").trim().split("\n").slice(-30);
|
|
8265
8288
|
recent = lines.map((l) => {
|
|
@@ -8330,8 +8353,8 @@ function _loadOpenCodeProviders() {
|
|
|
8330
8353
|
function _readOpenCodeConfigObject(dir) {
|
|
8331
8354
|
const jsonPath = join15(dir, "opencode.json");
|
|
8332
8355
|
const jsoncPath = join15(dir, "opencode.jsonc");
|
|
8333
|
-
if (
|
|
8334
|
-
if (
|
|
8356
|
+
if (existsSync14(jsonPath)) return safeJsonParse3(readFileSync15(jsonPath, "utf-8"));
|
|
8357
|
+
if (existsSync14(jsoncPath)) return _parseJsonc(readFileSync15(jsoncPath, "utf-8"));
|
|
8335
8358
|
return {};
|
|
8336
8359
|
}
|
|
8337
8360
|
function _parseJsonc(raw) {
|
|
@@ -8356,11 +8379,11 @@ function _modelTier2(id2) {
|
|
|
8356
8379
|
}
|
|
8357
8380
|
function backupFile(path, label) {
|
|
8358
8381
|
try {
|
|
8359
|
-
if (!
|
|
8382
|
+
if (!existsSync14(path)) return null;
|
|
8360
8383
|
const bkDir = join15(USER_HOME2, ".claude", ".backups");
|
|
8361
|
-
|
|
8362
|
-
const bk = join15(bkDir, `${
|
|
8363
|
-
|
|
8384
|
+
mkdirSync10(bkDir, { recursive: true });
|
|
8385
|
+
const bk = join15(bkDir, `${basename8(path)}.${label}.${Date.now()}.bak`);
|
|
8386
|
+
copyFileSync5(path, bk);
|
|
8364
8387
|
return bk;
|
|
8365
8388
|
} catch {
|
|
8366
8389
|
return null;
|
|
@@ -8382,7 +8405,7 @@ function loadMcpPort() {
|
|
|
8382
8405
|
return n;
|
|
8383
8406
|
}
|
|
8384
8407
|
try {
|
|
8385
|
-
if (
|
|
8408
|
+
if (existsSync14(TIERS_FILE2)) {
|
|
8386
8409
|
const tiers = safeJsonParse3(readFileSync15(TIERS_FILE2, "utf-8"));
|
|
8387
8410
|
const cfg = tiers?.selection?.mcp_port ?? tiers?.mcp_port;
|
|
8388
8411
|
if (cfg === false || cfg === "disabled" || cfg === 0) return 0;
|
|
@@ -8395,14 +8418,14 @@ function loadMcpPort() {
|
|
|
8395
8418
|
}
|
|
8396
8419
|
function persistMcpPort(port) {
|
|
8397
8420
|
try {
|
|
8398
|
-
if (!
|
|
8421
|
+
if (!existsSync14(TIERS_FILE2)) return;
|
|
8399
8422
|
const tiers = safeJsonParse3(readFileSync15(TIERS_FILE2, "utf-8"));
|
|
8400
8423
|
tiers.selection ??= {};
|
|
8401
8424
|
if (Number(tiers.selection.mcp_port) === Number(port)) return;
|
|
8402
8425
|
tiers.selection.mcp_port = port;
|
|
8403
|
-
|
|
8426
|
+
mkdirSync10(dirname8(TIERS_FILE2), { recursive: true });
|
|
8404
8427
|
const tmp = TIERS_FILE2 + ".tmp." + Date.now();
|
|
8405
|
-
|
|
8428
|
+
writeFileSync12(tmp, JSON.stringify(tiers, null, 2) + "\n", "utf-8");
|
|
8406
8429
|
renameSync6(tmp, TIERS_FILE2);
|
|
8407
8430
|
} catch {
|
|
8408
8431
|
}
|
|
@@ -8592,12 +8615,12 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8592
8615
|
} else {
|
|
8593
8616
|
console.error("[vibeOS] NO MODEL \u2014 enforcement disabled, will auto-detect on first hook");
|
|
8594
8617
|
}
|
|
8595
|
-
console.error(`[vibeOS] auto-config guard: currentModel=${currentModel ? "SET" : "NONE"}, TIERS_FILE=${TIERS_FILE2}, exists=${
|
|
8596
|
-
if (currentModel || !
|
|
8618
|
+
console.error(`[vibeOS] auto-config guard: currentModel=${currentModel ? "SET" : "NONE"}, TIERS_FILE=${TIERS_FILE2}, exists=${existsSync14(TIERS_FILE2)}`);
|
|
8619
|
+
if (currentModel || !existsSync14(TIERS_FILE2)) {
|
|
8597
8620
|
try {
|
|
8598
8621
|
let _tiersData2;
|
|
8599
8622
|
let _wasCorrupted = false;
|
|
8600
|
-
if (
|
|
8623
|
+
if (existsSync14(TIERS_FILE2)) {
|
|
8601
8624
|
try {
|
|
8602
8625
|
_tiersData2 = safeJsonParse3(readFileSync15(TIERS_FILE2, "utf-8"));
|
|
8603
8626
|
} catch {
|
|
@@ -8670,9 +8693,9 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8670
8693
|
if (_tiersData2) {
|
|
8671
8694
|
_tiersData2.selection ??= {};
|
|
8672
8695
|
if (_tiersData2.selection.mcp_port === void 0) _tiersData2.selection.mcp_port = 9578;
|
|
8673
|
-
|
|
8696
|
+
mkdirSync10(dirname8(TIERS_FILE2), { recursive: true });
|
|
8674
8697
|
const _tmp = TIERS_FILE2 + ".tmp." + Date.now();
|
|
8675
|
-
|
|
8698
|
+
writeFileSync12(_tmp, JSON.stringify(_tiersData2, null, 2) + "\n", "utf-8");
|
|
8676
8699
|
renameSync6(_tmp, TIERS_FILE2);
|
|
8677
8700
|
console.error(`[vibeOS] auto-synced model-tiers.json: brain=${_brain.id} medium=${_tiersData2.trinity?.medium?.oc || ""} cheap=${_tiersData2.trinity?.cheap?.oc || ""}`);
|
|
8678
8701
|
const _tiersCfg = safeJsonParse3(readFileSync15(TIERS_FILE2, "utf-8"));
|
|
@@ -8696,7 +8719,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8696
8719
|
if (_mt.selection && (_mt.selection.mcp_port === void 0 || _mt.selection.mcp_port === null)) {
|
|
8697
8720
|
_mt.selection.mcp_port = 9578;
|
|
8698
8721
|
const _tmp = TIERS_FILE2 + ".tmp." + Date.now();
|
|
8699
|
-
|
|
8722
|
+
writeFileSync12(_tmp, JSON.stringify(_mt, null, 2) + "\n", "utf-8");
|
|
8700
8723
|
renameSync6(_tmp, TIERS_FILE2);
|
|
8701
8724
|
}
|
|
8702
8725
|
} catch {
|
|
@@ -8716,7 +8739,7 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8716
8739
|
console.error(`[vibeOS] project-memory init failed for ${fp}: ${err.message}`);
|
|
8717
8740
|
}
|
|
8718
8741
|
try {
|
|
8719
|
-
if (directory3 &&
|
|
8742
|
+
if (directory3 && existsSync14(directory3)) {
|
|
8720
8743
|
const techStack = detectTechStack(directory3);
|
|
8721
8744
|
const result = ensureProjectDocs(directory3, techStack);
|
|
8722
8745
|
if (result.created.length > 0) console.error(`[vibeOS] Project Guard: created ${result.created.join(", ")}`);
|
|
@@ -8754,8 +8777,8 @@ async function DelegationEnforcer({ client: client2, directory: directory3 } = {
|
|
|
8754
8777
|
directory: directory3,
|
|
8755
8778
|
safeJsonParse: safeJsonParse3,
|
|
8756
8779
|
readFileSync: readFileSync15,
|
|
8757
|
-
writeFileSync:
|
|
8758
|
-
existsSync:
|
|
8780
|
+
writeFileSync: writeFileSync12,
|
|
8781
|
+
existsSync: existsSync14,
|
|
8759
8782
|
renameSync: renameSync6,
|
|
8760
8783
|
TIERS_FILE: TIERS_FILE2,
|
|
8761
8784
|
USER_HOME: USER_HOME2,
|
|
@@ -8954,7 +8977,7 @@ ${report.narrative}`);
|
|
|
8954
8977
|
getSessionMetrics: () => computeSessionMetrics(readFullState(), _OC_SID),
|
|
8955
8978
|
getTodos: () => loadTodos(),
|
|
8956
8979
|
listReports: (filter) => {
|
|
8957
|
-
if (!
|
|
8980
|
+
if (!existsSync14(REPORTS_DIR)) {
|
|
8958
8981
|
const e = new Error("reports dir not found");
|
|
8959
8982
|
e.status = 404;
|
|
8960
8983
|
throw e;
|