helixevo 0.7.0 → 0.8.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 +14 -0
- package/README.md +14 -6
- package/dashboard/app/api/run/route.ts +19 -1
- package/dashboard/app/commands/page.tsx +28 -4
- package/dashboard/app/guide/page.tsx +44 -5
- package/dashboard/app/page.tsx +166 -6
- package/dashboard/app/proof/client.tsx +56 -3
- package/dashboard/app/topology/client.tsx +39 -0
- package/dashboard/lib/data.ts +177 -0
- package/dashboard/lib/release-spotlight.ts +10 -10
- package/dist/cli.js +1249 -225
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2075,28 +2075,93 @@ function ensureDir(dir) {
|
|
|
2075
2075
|
mkdirSync(dir, { recursive: true });
|
|
2076
2076
|
}
|
|
2077
2077
|
}
|
|
2078
|
+
function providerConfigKey(provider) {
|
|
2079
|
+
if (provider === "claude-code")
|
|
2080
|
+
return "claudeCode";
|
|
2081
|
+
if (provider === "codex")
|
|
2082
|
+
return "codex";
|
|
2083
|
+
return "ollama";
|
|
2084
|
+
}
|
|
2085
|
+
function uniqueProviders(providers) {
|
|
2086
|
+
return [...new Set(providers.filter((provider) => provider === "claude-code" || provider === "codex" || provider === "ollama"))];
|
|
2087
|
+
}
|
|
2088
|
+
function syncTopLevelModels(config) {
|
|
2089
|
+
const providerKey = providerConfigKey(config.llm.defaultProvider);
|
|
2090
|
+
const providerConfig = config.llm.providers[providerKey];
|
|
2091
|
+
return {
|
|
2092
|
+
...config,
|
|
2093
|
+
model: providerConfig.model,
|
|
2094
|
+
judgeModel: providerConfig.judgeModel
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
function normalizeConfig(raw) {
|
|
2098
|
+
const input = raw ?? {};
|
|
2099
|
+
const inputProviders = input.llm?.providers;
|
|
2100
|
+
const claudeCode = {
|
|
2101
|
+
...DEFAULT_CONFIG.llm.providers.claudeCode,
|
|
2102
|
+
...inputProviders?.claudeCode ?? {},
|
|
2103
|
+
model: inputProviders?.claudeCode?.model ?? input.model ?? DEFAULT_CONFIG.llm.providers.claudeCode.model,
|
|
2104
|
+
judgeModel: inputProviders?.claudeCode?.judgeModel ?? input.judgeModel ?? DEFAULT_CONFIG.llm.providers.claudeCode.judgeModel
|
|
2105
|
+
};
|
|
2106
|
+
const codex = {
|
|
2107
|
+
...DEFAULT_CONFIG.llm.providers.codex,
|
|
2108
|
+
...inputProviders?.codex ?? {}
|
|
2109
|
+
};
|
|
2110
|
+
const ollama = {
|
|
2111
|
+
...DEFAULT_CONFIG.llm.providers.ollama,
|
|
2112
|
+
...inputProviders?.ollama ?? {}
|
|
2113
|
+
};
|
|
2114
|
+
const fallbackOrder = uniqueProviders(input.llm?.fallbackOrder ?? DEFAULT_CONFIG.llm.fallbackOrder);
|
|
2115
|
+
const normalized = {
|
|
2116
|
+
model: input.model ?? DEFAULT_CONFIG.model,
|
|
2117
|
+
judgeModel: input.judgeModel ?? DEFAULT_CONFIG.judgeModel,
|
|
2118
|
+
llm: {
|
|
2119
|
+
defaultProvider: input.llm?.defaultProvider ?? DEFAULT_CONFIG.llm.defaultProvider,
|
|
2120
|
+
fallbackPolicy: input.llm?.fallbackPolicy ?? DEFAULT_CONFIG.llm.fallbackPolicy,
|
|
2121
|
+
fallbackOrder,
|
|
2122
|
+
providers: {
|
|
2123
|
+
claudeCode,
|
|
2124
|
+
codex,
|
|
2125
|
+
ollama
|
|
2126
|
+
}
|
|
2127
|
+
},
|
|
2128
|
+
evolution: {
|
|
2129
|
+
...DEFAULT_CONFIG.evolution,
|
|
2130
|
+
...input.evolution ?? {}
|
|
2131
|
+
},
|
|
2132
|
+
quality: {
|
|
2133
|
+
...DEFAULT_CONFIG.quality,
|
|
2134
|
+
...input.quality ?? {}
|
|
2135
|
+
},
|
|
2136
|
+
reporting: {
|
|
2137
|
+
...DEFAULT_CONFIG.reporting,
|
|
2138
|
+
...input.reporting ?? {}
|
|
2139
|
+
},
|
|
2140
|
+
paths: {
|
|
2141
|
+
...DEFAULT_CONFIG.paths,
|
|
2142
|
+
...input.paths ?? {}
|
|
2143
|
+
}
|
|
2144
|
+
};
|
|
2145
|
+
if (normalized.paths.data.includes(".skillgraph")) {
|
|
2146
|
+
normalized.paths.data = HELIX_DIR;
|
|
2147
|
+
}
|
|
2148
|
+
if (normalized.paths.generalSkills.includes(".skillgraph")) {
|
|
2149
|
+
normalized.paths.generalSkills = join(HELIX_DIR, "general");
|
|
2150
|
+
}
|
|
2151
|
+
normalized.llm.fallbackOrder = uniqueProviders(normalized.llm.fallbackOrder.filter((provider) => provider !== normalized.llm.defaultProvider));
|
|
2152
|
+
return syncTopLevelModels(normalized);
|
|
2153
|
+
}
|
|
2078
2154
|
function loadConfig() {
|
|
2079
2155
|
if (_configCache)
|
|
2080
2156
|
return _configCache;
|
|
2081
|
-
|
|
2082
|
-
if (!existsSync(CONFIG_PATH)) {
|
|
2083
|
-
config = DEFAULT_CONFIG;
|
|
2084
|
-
} else {
|
|
2085
|
-
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
2086
|
-
config = { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
|
|
2087
|
-
}
|
|
2088
|
-
if (config.paths.data.includes(".skillgraph")) {
|
|
2089
|
-
config.paths.data = HELIX_DIR;
|
|
2090
|
-
}
|
|
2091
|
-
if (config.paths.generalSkills.includes(".skillgraph")) {
|
|
2092
|
-
config.paths.generalSkills = join(HELIX_DIR, "general");
|
|
2093
|
-
}
|
|
2157
|
+
const config = existsSync(CONFIG_PATH) ? normalizeConfig(JSON.parse(readFileSync(CONFIG_PATH, "utf-8"))) : normalizeConfig(undefined);
|
|
2094
2158
|
_configCache = config;
|
|
2095
2159
|
return config;
|
|
2096
2160
|
}
|
|
2097
2161
|
function saveConfig(config) {
|
|
2098
2162
|
ensureDir(HELIX_DIR);
|
|
2099
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
2163
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(normalizeConfig(config), null, 2));
|
|
2164
|
+
_configCache = null;
|
|
2100
2165
|
}
|
|
2101
2166
|
function getDataPath(filename) {
|
|
2102
2167
|
const config = loadConfig();
|
|
@@ -2113,6 +2178,32 @@ var init_config = __esm(() => {
|
|
|
2113
2178
|
DEFAULT_CONFIG = {
|
|
2114
2179
|
model: "sonnet",
|
|
2115
2180
|
judgeModel: "sonnet",
|
|
2181
|
+
llm: {
|
|
2182
|
+
defaultProvider: "claude-code",
|
|
2183
|
+
fallbackPolicy: "disabled",
|
|
2184
|
+
fallbackOrder: [],
|
|
2185
|
+
providers: {
|
|
2186
|
+
claudeCode: {
|
|
2187
|
+
enabled: true,
|
|
2188
|
+
command: "claude",
|
|
2189
|
+
model: "sonnet",
|
|
2190
|
+
judgeModel: "sonnet"
|
|
2191
|
+
},
|
|
2192
|
+
codex: {
|
|
2193
|
+
enabled: false,
|
|
2194
|
+
command: "codex",
|
|
2195
|
+
model: "gpt-5-codex",
|
|
2196
|
+
judgeModel: "gpt-5-codex"
|
|
2197
|
+
},
|
|
2198
|
+
ollama: {
|
|
2199
|
+
enabled: false,
|
|
2200
|
+
command: "ollama",
|
|
2201
|
+
host: "http://127.0.0.1:11434",
|
|
2202
|
+
model: "qwen3-coder:latest",
|
|
2203
|
+
judgeModel: "qwen3-coder:latest"
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
},
|
|
2116
2207
|
evolution: {
|
|
2117
2208
|
schedule: "0 2 * * *",
|
|
2118
2209
|
generalizationSchedule: "0 3 * * 5",
|
|
@@ -9647,6 +9738,68 @@ function loadGovernanceState() {
|
|
|
9647
9738
|
return state;
|
|
9648
9739
|
return { ...DEFAULT_GOVERNANCE_STATE, ...state, selectionMode: state.selectionMode === "manual" && state.manualMode ? "manual" : "auto" };
|
|
9649
9740
|
}
|
|
9741
|
+
function providerSnapshotDefaults(provider) {
|
|
9742
|
+
const config = loadConfig();
|
|
9743
|
+
const providerConfig = provider === "claude-code" ? config.llm.providers.claudeCode : provider === "codex" ? config.llm.providers.codex : config.llm.providers.ollama;
|
|
9744
|
+
return {
|
|
9745
|
+
provider,
|
|
9746
|
+
configured: providerConfig.enabled,
|
|
9747
|
+
enabled: providerConfig.enabled,
|
|
9748
|
+
installed: false,
|
|
9749
|
+
available: false,
|
|
9750
|
+
status: "unknown",
|
|
9751
|
+
model: providerConfig.model,
|
|
9752
|
+
judgeModel: providerConfig.judgeModel,
|
|
9753
|
+
summary: providerConfig.enabled ? `No ${provider} execution has been recorded yet.` : `${provider} is installed as an optional provider but not enabled in config.`,
|
|
9754
|
+
nextStep: providerConfig.enabled ? `Run a provider-backed command or refresh provider status to record live ${provider} health.` : `Enable ${provider} in ~/.helix/config.json before selecting it.`,
|
|
9755
|
+
notices: []
|
|
9756
|
+
};
|
|
9757
|
+
}
|
|
9758
|
+
function defaultLlmRuntimeState() {
|
|
9759
|
+
const config = loadConfig();
|
|
9760
|
+
return {
|
|
9761
|
+
updatedAt: new Date(0).toISOString(),
|
|
9762
|
+
defaultProvider: config.llm.defaultProvider,
|
|
9763
|
+
fallbackPolicy: config.llm.fallbackPolicy,
|
|
9764
|
+
fallbackOrder: [...config.llm.fallbackOrder],
|
|
9765
|
+
providers: {
|
|
9766
|
+
"claude-code": providerSnapshotDefaults("claude-code"),
|
|
9767
|
+
codex: providerSnapshotDefaults("codex"),
|
|
9768
|
+
ollama: providerSnapshotDefaults("ollama")
|
|
9769
|
+
}
|
|
9770
|
+
};
|
|
9771
|
+
}
|
|
9772
|
+
function loadLlmRuntimeState() {
|
|
9773
|
+
const defaults = defaultLlmRuntimeState();
|
|
9774
|
+
const stored = readJson("llm-runtime-state.json", defaults);
|
|
9775
|
+
return {
|
|
9776
|
+
...defaults,
|
|
9777
|
+
...stored,
|
|
9778
|
+
defaultProvider: loadConfig().llm.defaultProvider,
|
|
9779
|
+
fallbackPolicy: loadConfig().llm.fallbackPolicy,
|
|
9780
|
+
fallbackOrder: [...loadConfig().llm.fallbackOrder],
|
|
9781
|
+
providers: {
|
|
9782
|
+
"claude-code": {
|
|
9783
|
+
...defaults.providers["claude-code"],
|
|
9784
|
+
...stored.providers?.["claude-code"] ?? {}
|
|
9785
|
+
},
|
|
9786
|
+
codex: {
|
|
9787
|
+
...defaults.providers.codex,
|
|
9788
|
+
...stored.providers?.codex ?? {}
|
|
9789
|
+
},
|
|
9790
|
+
ollama: {
|
|
9791
|
+
...defaults.providers.ollama,
|
|
9792
|
+
...stored.providers?.ollama ?? {}
|
|
9793
|
+
}
|
|
9794
|
+
}
|
|
9795
|
+
};
|
|
9796
|
+
}
|
|
9797
|
+
function saveLlmRuntimeState(state) {
|
|
9798
|
+
writeJson("llm-runtime-state.json", state);
|
|
9799
|
+
}
|
|
9800
|
+
function saveTopologyOptimizeStatus(status) {
|
|
9801
|
+
writeJson("topology-optimize-status.json", status);
|
|
9802
|
+
}
|
|
9650
9803
|
function loadStoredTopologyReviewCandidates() {
|
|
9651
9804
|
return readJson("topology-review-candidates.json", []).slice().sort((a, b) => b.lastObservedAt.localeCompare(a.lastObservedAt) || a.title.localeCompare(b.title));
|
|
9652
9805
|
}
|
|
@@ -11559,6 +11712,40 @@ var init_data = __esm(() => {
|
|
|
11559
11712
|
|
|
11560
11713
|
// src/utils/llm.ts
|
|
11561
11714
|
import { spawn } from "node:child_process";
|
|
11715
|
+
import { existsSync as existsSync4, mkdtempSync, readFileSync as readFileSync4, rmSync } from "node:fs";
|
|
11716
|
+
import { join as join4 } from "node:path";
|
|
11717
|
+
import { tmpdir } from "node:os";
|
|
11718
|
+
function providerConfigKey2(provider) {
|
|
11719
|
+
if (provider === "claude-code")
|
|
11720
|
+
return "claudeCode";
|
|
11721
|
+
if (provider === "codex")
|
|
11722
|
+
return "codex";
|
|
11723
|
+
return "ollama";
|
|
11724
|
+
}
|
|
11725
|
+
function providerLabel(provider) {
|
|
11726
|
+
return PROVIDER_LABELS[provider];
|
|
11727
|
+
}
|
|
11728
|
+
function getProviderLabel(provider) {
|
|
11729
|
+
return providerLabel(provider);
|
|
11730
|
+
}
|
|
11731
|
+
function getProviderConfig(config, provider) {
|
|
11732
|
+
return config.llm.providers[providerConfigKey2(provider)];
|
|
11733
|
+
}
|
|
11734
|
+
function supportsCapability(provider, capability) {
|
|
11735
|
+
return PROVIDER_CAPABILITIES[provider].includes(capability);
|
|
11736
|
+
}
|
|
11737
|
+
function capabilityScope(capability) {
|
|
11738
|
+
return capability === "web-search" ? "claude-only" : "shared";
|
|
11739
|
+
}
|
|
11740
|
+
function buildSystemPrompt(system, prompt) {
|
|
11741
|
+
if (!system)
|
|
11742
|
+
return prompt;
|
|
11743
|
+
return `System instructions:
|
|
11744
|
+
${system}
|
|
11745
|
+
|
|
11746
|
+
User request:
|
|
11747
|
+
${prompt}`;
|
|
11748
|
+
}
|
|
11562
11749
|
function buildClaudeEnv({ dropOauthToken }) {
|
|
11563
11750
|
const env = { ...process.env };
|
|
11564
11751
|
if (dropOauthToken) {
|
|
@@ -11566,22 +11753,71 @@ function buildClaudeEnv({ dropOauthToken }) {
|
|
|
11566
11753
|
}
|
|
11567
11754
|
return env;
|
|
11568
11755
|
}
|
|
11569
|
-
function
|
|
11756
|
+
function classifyClaudeFailure(error) {
|
|
11757
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11758
|
+
if (CLAUDE_AUTH_ERROR_PATTERNS.some((pattern) => message.includes(pattern)))
|
|
11759
|
+
return "auth";
|
|
11760
|
+
if (message.includes("timed out"))
|
|
11761
|
+
return "timeout";
|
|
11762
|
+
if (message.includes("Failed to spawn claude"))
|
|
11763
|
+
return "spawn";
|
|
11764
|
+
return "unknown";
|
|
11765
|
+
}
|
|
11766
|
+
function classifyCodexFailure(error) {
|
|
11767
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
11768
|
+
if (CODEX_AUTH_ERROR_PATTERNS.some((pattern) => message.includes(pattern.toLowerCase())))
|
|
11769
|
+
return "auth";
|
|
11770
|
+
if (message.includes("timed out"))
|
|
11771
|
+
return "timeout";
|
|
11772
|
+
if (message.includes("failed to spawn codex") || message.includes("enoent"))
|
|
11773
|
+
return "spawn";
|
|
11774
|
+
return "unknown";
|
|
11775
|
+
}
|
|
11776
|
+
function classifyOllamaFailure(error) {
|
|
11777
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
11778
|
+
if (OLLAMA_MODEL_MISSING_PATTERNS.some((pattern) => message.includes(pattern)))
|
|
11779
|
+
return "model-missing";
|
|
11780
|
+
if (message.includes("econnrefused") || message.includes("connect") || message.includes("network"))
|
|
11781
|
+
return "network";
|
|
11782
|
+
if (message.includes("timed out") || message.includes("aborterror"))
|
|
11783
|
+
return "timeout";
|
|
11784
|
+
return "unknown";
|
|
11785
|
+
}
|
|
11786
|
+
function hasBinary(command) {
|
|
11787
|
+
return new Promise((resolve) => {
|
|
11788
|
+
const proc = spawn(command, ["--help"], { stdio: "ignore" });
|
|
11789
|
+
let settled = false;
|
|
11790
|
+
proc.on("error", () => {
|
|
11791
|
+
if (!settled) {
|
|
11792
|
+
settled = true;
|
|
11793
|
+
resolve(false);
|
|
11794
|
+
}
|
|
11795
|
+
});
|
|
11796
|
+
proc.on("close", () => {
|
|
11797
|
+
if (!settled) {
|
|
11798
|
+
settled = true;
|
|
11799
|
+
resolve(true);
|
|
11800
|
+
}
|
|
11801
|
+
});
|
|
11802
|
+
});
|
|
11803
|
+
}
|
|
11804
|
+
function runProcess(command, args, options) {
|
|
11570
11805
|
return new Promise((resolve, reject) => {
|
|
11571
|
-
const proc = spawn(
|
|
11806
|
+
const proc = spawn(command, args, {
|
|
11572
11807
|
stdio: ["pipe", "pipe", "pipe"],
|
|
11573
|
-
env
|
|
11808
|
+
env: options?.env
|
|
11574
11809
|
});
|
|
11575
11810
|
let stdout = "";
|
|
11576
11811
|
let stderr = "";
|
|
11577
11812
|
let settled = false;
|
|
11813
|
+
const timeoutMs = options?.timeoutMs ?? 180000;
|
|
11578
11814
|
const timeout = setTimeout(() => {
|
|
11579
11815
|
proc.kill();
|
|
11580
11816
|
if (!settled) {
|
|
11581
11817
|
settled = true;
|
|
11582
|
-
reject(new Error(
|
|
11818
|
+
reject(new Error(`${command} call timed out after ${Math.round(timeoutMs / 1000)}s`));
|
|
11583
11819
|
}
|
|
11584
|
-
},
|
|
11820
|
+
}, timeoutMs);
|
|
11585
11821
|
proc.stdout.on("data", (data) => {
|
|
11586
11822
|
stdout += data.toString();
|
|
11587
11823
|
});
|
|
@@ -11594,10 +11830,10 @@ function runClaudeOnce(prompt, args, env) {
|
|
|
11594
11830
|
return;
|
|
11595
11831
|
settled = true;
|
|
11596
11832
|
if (code !== 0) {
|
|
11597
|
-
const output = (stderr || stdout).slice(0,
|
|
11598
|
-
reject(new Error(
|
|
11833
|
+
const output = (stderr || stdout || `No output from ${command}`).slice(0, 1500);
|
|
11834
|
+
reject(new Error(`${command} exited with code ${code}: ${output}`));
|
|
11599
11835
|
} else {
|
|
11600
|
-
resolve(stdout.trim());
|
|
11836
|
+
resolve({ stdout: stdout.trim(), stderr: stderr.trim() });
|
|
11601
11837
|
}
|
|
11602
11838
|
});
|
|
11603
11839
|
proc.on("error", (err) => {
|
|
@@ -11605,47 +11841,630 @@ function runClaudeOnce(prompt, args, env) {
|
|
|
11605
11841
|
if (settled)
|
|
11606
11842
|
return;
|
|
11607
11843
|
settled = true;
|
|
11608
|
-
reject(new Error(`Failed to spawn
|
|
11844
|
+
reject(new Error(`Failed to spawn ${command}: ${err.message}`));
|
|
11609
11845
|
});
|
|
11610
|
-
|
|
11846
|
+
if (options?.stdin)
|
|
11847
|
+
proc.stdin.write(options.stdin);
|
|
11611
11848
|
proc.stdin.end();
|
|
11612
11849
|
});
|
|
11613
11850
|
}
|
|
11614
|
-
function
|
|
11615
|
-
|
|
11616
|
-
|
|
11851
|
+
async function readClaudeAuthStatus(command, env) {
|
|
11852
|
+
try {
|
|
11853
|
+
const result = await runProcess(command, ["auth", "status"], { env, timeoutMs: 1e4 });
|
|
11854
|
+
const raw = (result.stdout || result.stderr).trim();
|
|
11855
|
+
if (!raw)
|
|
11856
|
+
return null;
|
|
11857
|
+
return JSON.parse(raw);
|
|
11858
|
+
} catch {
|
|
11859
|
+
return null;
|
|
11860
|
+
}
|
|
11861
|
+
}
|
|
11862
|
+
function summarizeAuthStatus(status) {
|
|
11863
|
+
if (!status)
|
|
11864
|
+
return "Unable to read `claude auth status` output.";
|
|
11865
|
+
if (status.loggedIn) {
|
|
11866
|
+
const method = status.authMethod ? ` via ${status.authMethod}` : "";
|
|
11867
|
+
const plan = status.subscriptionType ? ` (${status.subscriptionType})` : "";
|
|
11868
|
+
return `\`claude auth status\` reports logged in${method}${plan}, but live execution still returned unauthorized.`;
|
|
11869
|
+
}
|
|
11870
|
+
return "`claude auth status` does not report an active login.";
|
|
11871
|
+
}
|
|
11872
|
+
function buildClaudeAuthFailureMessage(params) {
|
|
11873
|
+
const firstMessage = params.firstError instanceof Error ? params.firstError.message : String(params.firstError);
|
|
11874
|
+
const finalMessage = params.finalError instanceof Error ? params.finalError.message : String(params.finalError);
|
|
11875
|
+
const lines = [
|
|
11876
|
+
"Claude execution failed because authentication is not healthy.",
|
|
11877
|
+
params.hadOauthToken ? "Retry without inherited `CLAUDE_CODE_OAUTH_TOKEN` was attempted, but execution still failed." : "No inherited `CLAUDE_CODE_OAUTH_TOKEN` was available to strip before failure classification.",
|
|
11878
|
+
summarizeAuthStatus(params.authStatus),
|
|
11879
|
+
`Next step: run \`${params.command} auth login\` to refresh credentials, then retry the command.`
|
|
11880
|
+
];
|
|
11881
|
+
if (params.hadOauthToken) {
|
|
11882
|
+
lines.push("If you export `CLAUDE_CODE_OAUTH_TOKEN` in your shell, remove it so managed Claude auth can take over.");
|
|
11883
|
+
}
|
|
11884
|
+
lines.push("");
|
|
11885
|
+
lines.push(`First failure: ${firstMessage.slice(0, 500)}`);
|
|
11886
|
+
if (finalMessage !== firstMessage)
|
|
11887
|
+
lines.push(`Final failure: ${finalMessage.slice(0, 500)}`);
|
|
11888
|
+
return lines.join(`
|
|
11889
|
+
`);
|
|
11890
|
+
}
|
|
11891
|
+
function buildCodexFailureMessage(kind, error, command) {
|
|
11892
|
+
const baseMessage = error instanceof Error ? error.message : String(error);
|
|
11893
|
+
if (kind === "auth") {
|
|
11894
|
+
return new ProviderCommandError({
|
|
11895
|
+
provider: "codex",
|
|
11896
|
+
kind,
|
|
11897
|
+
message: [
|
|
11898
|
+
"Codex execution failed because authentication is not healthy.",
|
|
11899
|
+
`Next step: run \`${command} login\` to refresh Codex credentials, then retry the command.`,
|
|
11900
|
+
"",
|
|
11901
|
+
`Failure: ${baseMessage.slice(0, 500)}`
|
|
11902
|
+
].join(`
|
|
11903
|
+
`),
|
|
11904
|
+
nextStep: `Run \`${command} login\` to refresh Codex credentials, then retry.`
|
|
11905
|
+
});
|
|
11906
|
+
}
|
|
11907
|
+
if (kind === "spawn") {
|
|
11908
|
+
return new ProviderCommandError({
|
|
11909
|
+
provider: "codex",
|
|
11910
|
+
kind,
|
|
11911
|
+
message: `Codex execution could not start. Ensure the \`${command}\` CLI is installed and available in PATH.
|
|
11912
|
+
|
|
11913
|
+
Failure: ${baseMessage.slice(0, 500)}`,
|
|
11914
|
+
nextStep: `Install or fix the \`${command}\` CLI, then retry.`
|
|
11915
|
+
});
|
|
11916
|
+
}
|
|
11917
|
+
return new ProviderCommandError({
|
|
11918
|
+
provider: "codex",
|
|
11919
|
+
kind,
|
|
11920
|
+
message: `Codex execution failed.
|
|
11921
|
+
|
|
11922
|
+
Failure: ${baseMessage.slice(0, 500)}`,
|
|
11923
|
+
nextStep: "Review the Codex CLI error and retry after fixing the provider state."
|
|
11924
|
+
});
|
|
11925
|
+
}
|
|
11926
|
+
function buildOllamaFailureMessage(kind, error, host, model) {
|
|
11927
|
+
const baseMessage = error instanceof Error ? error.message : String(error);
|
|
11928
|
+
if (kind === "model-missing") {
|
|
11929
|
+
return new ProviderCommandError({
|
|
11930
|
+
provider: "ollama",
|
|
11931
|
+
kind,
|
|
11932
|
+
message: `Ollama could not run model \`${model}\` because it is not available locally.
|
|
11933
|
+
|
|
11934
|
+
Failure: ${baseMessage.slice(0, 500)}`,
|
|
11935
|
+
nextStep: `Pull the model with \`ollama pull ${model}\` or change the configured Ollama model, then retry.`
|
|
11936
|
+
});
|
|
11937
|
+
}
|
|
11938
|
+
if (kind === "network") {
|
|
11939
|
+
return new ProviderCommandError({
|
|
11940
|
+
provider: "ollama",
|
|
11941
|
+
kind,
|
|
11942
|
+
message: `Ollama is not reachable at ${host}.
|
|
11943
|
+
|
|
11944
|
+
Failure: ${baseMessage.slice(0, 500)}`,
|
|
11945
|
+
nextStep: "Start the Ollama daemon or verify the configured host before retrying."
|
|
11946
|
+
});
|
|
11947
|
+
}
|
|
11948
|
+
return new ProviderCommandError({
|
|
11949
|
+
provider: "ollama",
|
|
11950
|
+
kind,
|
|
11951
|
+
message: `Ollama execution failed.
|
|
11952
|
+
|
|
11953
|
+
Failure: ${baseMessage.slice(0, 500)}`,
|
|
11954
|
+
nextStep: "Review the Ollama error, verify the daemon/model, and retry."
|
|
11955
|
+
});
|
|
11956
|
+
}
|
|
11957
|
+
async function runClaudeOnce(prompt, args, command, env) {
|
|
11958
|
+
const result = await runProcess(command, args, { stdin: prompt, env });
|
|
11959
|
+
return result.stdout.trim();
|
|
11617
11960
|
}
|
|
11618
|
-
async function
|
|
11961
|
+
async function runClaudeWithRecovery(prompt, args, command) {
|
|
11962
|
+
const inheritedEnv = buildClaudeEnv({ dropOauthToken: false });
|
|
11619
11963
|
try {
|
|
11620
|
-
return await runClaudeOnce(prompt, args,
|
|
11964
|
+
return await runClaudeOnce(prompt, args, command, inheritedEnv);
|
|
11621
11965
|
} catch (error) {
|
|
11622
|
-
if (
|
|
11966
|
+
if (classifyClaudeFailure(error) !== "auth") {
|
|
11623
11967
|
throw error;
|
|
11624
11968
|
}
|
|
11625
|
-
|
|
11969
|
+
const hadOauthToken = Boolean(process.env.CLAUDE_CODE_OAUTH_TOKEN);
|
|
11970
|
+
const scrubbedEnv = buildClaudeEnv({ dropOauthToken: true });
|
|
11971
|
+
if (!hadOauthToken) {
|
|
11972
|
+
const authStatus = await readClaudeAuthStatus(command, scrubbedEnv);
|
|
11973
|
+
throw new ProviderCommandError({
|
|
11974
|
+
provider: "claude-code",
|
|
11975
|
+
kind: "auth",
|
|
11976
|
+
message: buildClaudeAuthFailureMessage({
|
|
11977
|
+
command,
|
|
11978
|
+
hadOauthToken,
|
|
11979
|
+
authStatus,
|
|
11980
|
+
firstError: error,
|
|
11981
|
+
finalError: error
|
|
11982
|
+
}),
|
|
11983
|
+
nextStep: `Run \`${command} auth login\` to refresh credentials, then retry.`,
|
|
11984
|
+
loggedIn: authStatus?.loggedIn,
|
|
11985
|
+
authMethod: authStatus?.authMethod,
|
|
11986
|
+
apiProvider: authStatus?.apiProvider
|
|
11987
|
+
});
|
|
11988
|
+
}
|
|
11989
|
+
try {
|
|
11990
|
+
return await runClaudeOnce(prompt, args, command, scrubbedEnv);
|
|
11991
|
+
} catch (retryError) {
|
|
11992
|
+
if (classifyClaudeFailure(retryError) !== "auth") {
|
|
11993
|
+
throw retryError;
|
|
11994
|
+
}
|
|
11995
|
+
const authStatus = await readClaudeAuthStatus(command, scrubbedEnv);
|
|
11996
|
+
throw new ProviderCommandError({
|
|
11997
|
+
provider: "claude-code",
|
|
11998
|
+
kind: "auth",
|
|
11999
|
+
message: buildClaudeAuthFailureMessage({
|
|
12000
|
+
command,
|
|
12001
|
+
hadOauthToken,
|
|
12002
|
+
authStatus,
|
|
12003
|
+
firstError: error,
|
|
12004
|
+
finalError: retryError
|
|
12005
|
+
}),
|
|
12006
|
+
nextStep: `Run \`${command} auth login\` to refresh credentials, then retry.`,
|
|
12007
|
+
loggedIn: authStatus?.loggedIn,
|
|
12008
|
+
authMethod: authStatus?.authMethod,
|
|
12009
|
+
apiProvider: authStatus?.apiProvider
|
|
12010
|
+
});
|
|
12011
|
+
}
|
|
11626
12012
|
}
|
|
11627
12013
|
}
|
|
11628
|
-
async function
|
|
11629
|
-
const
|
|
11630
|
-
|
|
12014
|
+
async function runClaudeCapability(request, config) {
|
|
12015
|
+
const providerConfig = config.llm.providers.claudeCode;
|
|
12016
|
+
if (!providerConfig.enabled) {
|
|
12017
|
+
throw new ProviderCommandError({
|
|
12018
|
+
provider: "claude-code",
|
|
12019
|
+
kind: "not-configured",
|
|
12020
|
+
message: "Claude Code is disabled in ~/.helix/config.json.",
|
|
12021
|
+
nextStep: "Enable Claude Code in the Helix config or choose another provider for shared capabilities."
|
|
12022
|
+
});
|
|
12023
|
+
}
|
|
12024
|
+
const command = providerConfig.command;
|
|
12025
|
+
const model = request.model ?? (request.capability === "judge" ? providerConfig.judgeModel : providerConfig.model);
|
|
12026
|
+
if (request.capability === "web-search") {
|
|
12027
|
+
const args2 = [
|
|
12028
|
+
"--print",
|
|
12029
|
+
"--allowedTools",
|
|
12030
|
+
"WebSearch,WebFetch",
|
|
12031
|
+
"--no-session-persistence",
|
|
12032
|
+
"--dangerously-skip-permissions",
|
|
12033
|
+
"--max-turns",
|
|
12034
|
+
"8"
|
|
12035
|
+
];
|
|
12036
|
+
if (model)
|
|
12037
|
+
args2.push("--model", model);
|
|
12038
|
+
return runClaudeWithRecovery(request.prompt, args2, command);
|
|
12039
|
+
}
|
|
11631
12040
|
const args = [
|
|
11632
12041
|
"--print",
|
|
11633
|
-
"--model",
|
|
11634
|
-
model,
|
|
11635
12042
|
"--output-format",
|
|
11636
12043
|
"text",
|
|
11637
12044
|
"--no-session-persistence"
|
|
11638
12045
|
];
|
|
11639
|
-
if (
|
|
11640
|
-
args.push("--
|
|
12046
|
+
if (model)
|
|
12047
|
+
args.push("--model", model);
|
|
12048
|
+
if (request.system)
|
|
12049
|
+
args.push("--system-prompt", request.system);
|
|
12050
|
+
return runClaudeWithRecovery(request.prompt, args, command);
|
|
12051
|
+
}
|
|
12052
|
+
async function runCodexCapability(request, config) {
|
|
12053
|
+
const providerConfig = config.llm.providers.codex;
|
|
12054
|
+
if (!providerConfig.enabled) {
|
|
12055
|
+
throw new ProviderCommandError({
|
|
12056
|
+
provider: "codex",
|
|
12057
|
+
kind: "not-configured",
|
|
12058
|
+
message: "Codex is disabled in ~/.helix/config.json.",
|
|
12059
|
+
nextStep: "Enable Codex in the Helix config before selecting it."
|
|
12060
|
+
});
|
|
12061
|
+
}
|
|
12062
|
+
const tempDir = mkdtempSync(join4(tmpdir(), "helix-codex-"));
|
|
12063
|
+
const outputPath = join4(tempDir, "last-message.txt");
|
|
12064
|
+
const model = request.model ?? (request.capability === "judge" ? providerConfig.judgeModel : providerConfig.model);
|
|
12065
|
+
const prompt = buildSystemPrompt(request.system, request.prompt);
|
|
12066
|
+
const args = [
|
|
12067
|
+
"exec",
|
|
12068
|
+
"-c",
|
|
12069
|
+
'model_reasoning_effort="high"',
|
|
12070
|
+
"--skip-git-repo-check",
|
|
12071
|
+
"--sandbox",
|
|
12072
|
+
"read-only",
|
|
12073
|
+
"--output-last-message",
|
|
12074
|
+
outputPath,
|
|
12075
|
+
"--color",
|
|
12076
|
+
"never"
|
|
12077
|
+
];
|
|
12078
|
+
if (model)
|
|
12079
|
+
args.push("--model", model);
|
|
12080
|
+
args.push(prompt);
|
|
12081
|
+
try {
|
|
12082
|
+
await runProcess(providerConfig.command, args, { timeoutMs: 180000 });
|
|
12083
|
+
if (!existsSync4(outputPath)) {
|
|
12084
|
+
throw new Error("codex finished without writing an output file");
|
|
12085
|
+
}
|
|
12086
|
+
return readFileSync4(outputPath, "utf-8").trim();
|
|
12087
|
+
} catch (error) {
|
|
12088
|
+
throw buildCodexFailureMessage(classifyCodexFailure(error), error, providerConfig.command);
|
|
12089
|
+
} finally {
|
|
12090
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
12091
|
+
}
|
|
12092
|
+
}
|
|
12093
|
+
async function runOllamaCapability(request, config) {
|
|
12094
|
+
const providerConfig = config.llm.providers.ollama;
|
|
12095
|
+
if (!providerConfig.enabled) {
|
|
12096
|
+
throw new ProviderCommandError({
|
|
12097
|
+
provider: "ollama",
|
|
12098
|
+
kind: "not-configured",
|
|
12099
|
+
message: "Ollama is disabled in ~/.helix/config.json.",
|
|
12100
|
+
nextStep: "Enable Ollama in the Helix config before selecting it."
|
|
12101
|
+
});
|
|
12102
|
+
}
|
|
12103
|
+
const model = request.model ?? (request.capability === "judge" ? providerConfig.judgeModel : providerConfig.model);
|
|
12104
|
+
const prompt = buildSystemPrompt(request.system, request.prompt);
|
|
12105
|
+
try {
|
|
12106
|
+
const response = await fetch(`${providerConfig.host.replace(/\/$/, "")}/api/generate`, {
|
|
12107
|
+
method: "POST",
|
|
12108
|
+
headers: { "Content-Type": "application/json" },
|
|
12109
|
+
body: JSON.stringify({
|
|
12110
|
+
model,
|
|
12111
|
+
prompt,
|
|
12112
|
+
stream: false
|
|
12113
|
+
}),
|
|
12114
|
+
signal: AbortSignal.timeout(180000)
|
|
12115
|
+
});
|
|
12116
|
+
const raw = await response.text();
|
|
12117
|
+
if (!response.ok) {
|
|
12118
|
+
throw new Error(raw || `ollama request failed with status ${response.status}`);
|
|
12119
|
+
}
|
|
12120
|
+
const parsed = JSON.parse(raw);
|
|
12121
|
+
if (parsed.error)
|
|
12122
|
+
throw new Error(parsed.error);
|
|
12123
|
+
return (parsed.response ?? "").trim();
|
|
12124
|
+
} catch (error) {
|
|
12125
|
+
throw buildOllamaFailureMessage(classifyOllamaFailure(error), error, providerConfig.host, model);
|
|
12126
|
+
}
|
|
12127
|
+
}
|
|
12128
|
+
function initializeSnapshot(state, provider, config) {
|
|
12129
|
+
const providerConfig = getProviderConfig(config, provider);
|
|
12130
|
+
return {
|
|
12131
|
+
...state.providers[provider],
|
|
12132
|
+
provider,
|
|
12133
|
+
configured: providerConfig.enabled,
|
|
12134
|
+
enabled: providerConfig.enabled,
|
|
12135
|
+
model: providerConfig.model,
|
|
12136
|
+
judgeModel: providerConfig.judgeModel,
|
|
12137
|
+
notices: state.providers[provider]?.notices ?? []
|
|
12138
|
+
};
|
|
12139
|
+
}
|
|
12140
|
+
function saveRuntimeMutation(mutator) {
|
|
12141
|
+
const state = loadLlmRuntimeState();
|
|
12142
|
+
mutator(state);
|
|
12143
|
+
state.updatedAt = new Date().toISOString();
|
|
12144
|
+
saveLlmRuntimeState(state);
|
|
12145
|
+
return state;
|
|
12146
|
+
}
|
|
12147
|
+
function updateSnapshotOnFailure(params) {
|
|
12148
|
+
saveRuntimeMutation((state) => {
|
|
12149
|
+
const ranAt = new Date().toISOString();
|
|
12150
|
+
const snapshot = initializeSnapshot(state, params.provider, params.config);
|
|
12151
|
+
snapshot.installed = params.error.kind !== "spawn" && params.error.kind !== "not-installed" ? true : snapshot.installed;
|
|
12152
|
+
snapshot.available = false;
|
|
12153
|
+
snapshot.status = params.error.kind === "not-configured" ? "unavailable" : "degraded";
|
|
12154
|
+
snapshot.summary = params.error.warning ?? params.error.message.split(`
|
|
12155
|
+
`)[0] ?? `${providerLabel(params.provider)} failed.`;
|
|
12156
|
+
snapshot.nextStep = params.error.nextStep;
|
|
12157
|
+
snapshot.failureKind = params.error.kind;
|
|
12158
|
+
snapshot.warning = params.error.warning;
|
|
12159
|
+
snapshot.lastCheckedAt = ranAt;
|
|
12160
|
+
snapshot.lastSelectedAt = ranAt;
|
|
12161
|
+
snapshot.lastFailedAt = ranAt;
|
|
12162
|
+
snapshot.lastFailureMessage = params.error.message.slice(0, 600);
|
|
12163
|
+
snapshot.notices = [...params.error.notices];
|
|
12164
|
+
if (params.error.loggedIn !== undefined)
|
|
12165
|
+
snapshot.loggedIn = params.error.loggedIn;
|
|
12166
|
+
if (params.error.authMethod)
|
|
12167
|
+
snapshot.authMethod = params.error.authMethod;
|
|
12168
|
+
if (params.error.apiProvider)
|
|
12169
|
+
snapshot.apiProvider = params.error.apiProvider;
|
|
12170
|
+
state.providers[params.provider] = snapshot;
|
|
12171
|
+
});
|
|
12172
|
+
}
|
|
12173
|
+
function updateSnapshotOnSuccess(params) {
|
|
12174
|
+
saveRuntimeMutation((state) => {
|
|
12175
|
+
const ranAt = new Date().toISOString();
|
|
12176
|
+
const snapshot = initializeSnapshot(state, params.provider, params.config);
|
|
12177
|
+
snapshot.installed = true;
|
|
12178
|
+
snapshot.available = true;
|
|
12179
|
+
snapshot.status = params.warning ? "degraded" : "healthy";
|
|
12180
|
+
snapshot.summary = params.summary;
|
|
12181
|
+
snapshot.warning = params.warning;
|
|
12182
|
+
snapshot.failureKind = undefined;
|
|
12183
|
+
snapshot.nextStep = undefined;
|
|
12184
|
+
snapshot.lastCheckedAt = ranAt;
|
|
12185
|
+
snapshot.lastSelectedAt = ranAt;
|
|
12186
|
+
snapshot.lastUsedAt = ranAt;
|
|
12187
|
+
snapshot.lastSucceededAt = ranAt;
|
|
12188
|
+
snapshot.lastFailureMessage = undefined;
|
|
12189
|
+
snapshot.notices = params.warning ? [params.warning] : [];
|
|
12190
|
+
state.providers[params.provider] = snapshot;
|
|
12191
|
+
});
|
|
12192
|
+
}
|
|
12193
|
+
function recordExecution(params) {
|
|
12194
|
+
saveRuntimeMutation((state) => {
|
|
12195
|
+
state.defaultProvider = params.config.llm.defaultProvider;
|
|
12196
|
+
state.fallbackPolicy = params.config.llm.fallbackPolicy;
|
|
12197
|
+
state.fallbackOrder = [...params.config.llm.fallbackOrder];
|
|
12198
|
+
state.lastExecution = {
|
|
12199
|
+
capability: params.capability,
|
|
12200
|
+
scope: params.scope,
|
|
12201
|
+
selectionMode: params.selectionMode,
|
|
12202
|
+
selectedProvider: params.selectedProvider,
|
|
12203
|
+
attemptedProviders: [...params.attemptedProviders],
|
|
12204
|
+
usedProvider: params.usedProvider,
|
|
12205
|
+
fallbackUsed: params.fallbackUsed,
|
|
12206
|
+
success: params.success,
|
|
12207
|
+
ranAt: new Date().toISOString(),
|
|
12208
|
+
summary: params.summary,
|
|
12209
|
+
nextStep: params.nextStep,
|
|
12210
|
+
failureKind: params.failureKind,
|
|
12211
|
+
warning: params.warning
|
|
12212
|
+
};
|
|
12213
|
+
});
|
|
12214
|
+
}
|
|
12215
|
+
async function executeProvider(provider, request, config) {
|
|
12216
|
+
if (!supportsCapability(provider, request.capability)) {
|
|
12217
|
+
throw new ProviderCommandError({
|
|
12218
|
+
provider,
|
|
12219
|
+
kind: "unknown",
|
|
12220
|
+
message: `${providerLabel(provider)} does not support the ${request.capability} capability in HelixEvo yet.`,
|
|
12221
|
+
nextStep: request.capability === "web-search" ? "Use Claude Code for Claude-scoped web-search and research operations." : "Select a provider that supports this capability or keep Claude Code as the default."
|
|
12222
|
+
});
|
|
12223
|
+
}
|
|
12224
|
+
const providerConfig = getProviderConfig(config, provider);
|
|
12225
|
+
const binaryRequired = provider !== "ollama";
|
|
12226
|
+
if (binaryRequired && !await hasBinary(providerConfig.command)) {
|
|
12227
|
+
throw new ProviderCommandError({
|
|
12228
|
+
provider,
|
|
12229
|
+
kind: "not-installed",
|
|
12230
|
+
message: `${providerLabel(provider)} is not available because the \`${providerConfig.command}\` command could not be found.`,
|
|
12231
|
+
nextStep: `Install or fix the ${providerLabel(provider)} CLI, then retry.`
|
|
12232
|
+
});
|
|
12233
|
+
}
|
|
12234
|
+
const text = provider === "claude-code" ? await runClaudeCapability(request, config) : provider === "codex" ? await runCodexCapability(request, config) : await runOllamaCapability(request, config);
|
|
12235
|
+
return { text, provider };
|
|
12236
|
+
}
|
|
12237
|
+
function buildProviderSummary(params) {
|
|
12238
|
+
const capability = params.capability === "web-search" ? "Claude-scoped web search" : params.capability;
|
|
12239
|
+
if (params.fallbackUsed) {
|
|
12240
|
+
return `${capability} used fallback provider ${providerLabel(params.provider)} after ${providerLabel(params.selectedProvider)} degraded.`;
|
|
12241
|
+
}
|
|
12242
|
+
return `${capability} used ${providerLabel(params.provider)} successfully.`;
|
|
12243
|
+
}
|
|
12244
|
+
async function runWithProviderControl(request) {
|
|
12245
|
+
const config = loadConfig();
|
|
12246
|
+
const scope = capabilityScope(request.capability);
|
|
12247
|
+
const selectedProvider = scope === "claude-only" ? "claude-code" : request.provider ?? config.llm.defaultProvider;
|
|
12248
|
+
const selectionMode = request.provider ? "explicit" : "default";
|
|
12249
|
+
const allowFallback = scope === "shared" && request.allowFallback !== false && config.llm.fallbackPolicy === "on-failure";
|
|
12250
|
+
const attemptedProviders = [selectedProvider];
|
|
12251
|
+
if (allowFallback) {
|
|
12252
|
+
for (const provider of config.llm.fallbackOrder) {
|
|
12253
|
+
if (provider !== selectedProvider && supportsCapability(provider, request.capability))
|
|
12254
|
+
attemptedProviders.push(provider);
|
|
12255
|
+
}
|
|
12256
|
+
}
|
|
12257
|
+
let lastError = null;
|
|
12258
|
+
for (const provider of attemptedProviders) {
|
|
12259
|
+
try {
|
|
12260
|
+
const result = await executeProvider(provider, request, config);
|
|
12261
|
+
const fallbackUsed = provider !== selectedProvider;
|
|
12262
|
+
const summary2 = buildProviderSummary({
|
|
12263
|
+
provider,
|
|
12264
|
+
capability: request.capability,
|
|
12265
|
+
selectedProvider,
|
|
12266
|
+
fallbackUsed,
|
|
12267
|
+
attemptedProviders
|
|
12268
|
+
});
|
|
12269
|
+
updateSnapshotOnSuccess({
|
|
12270
|
+
provider,
|
|
12271
|
+
config,
|
|
12272
|
+
summary: summary2,
|
|
12273
|
+
warning: fallbackUsed ? `Fallback used after ${providerLabel(selectedProvider)} degraded.` : undefined
|
|
12274
|
+
});
|
|
12275
|
+
recordExecution({
|
|
12276
|
+
config,
|
|
12277
|
+
capability: request.capability,
|
|
12278
|
+
scope,
|
|
12279
|
+
selectionMode,
|
|
12280
|
+
selectedProvider,
|
|
12281
|
+
attemptedProviders,
|
|
12282
|
+
usedProvider: provider,
|
|
12283
|
+
fallbackUsed,
|
|
12284
|
+
success: true,
|
|
12285
|
+
summary: summary2,
|
|
12286
|
+
warning: fallbackUsed ? `Fallback used after ${providerLabel(selectedProvider)} degraded.` : undefined
|
|
12287
|
+
});
|
|
12288
|
+
return result.text;
|
|
12289
|
+
} catch (error) {
|
|
12290
|
+
const providerError = error instanceof ProviderCommandError ? error : new ProviderCommandError({
|
|
12291
|
+
provider,
|
|
12292
|
+
kind: provider === "claude-code" ? classifyClaudeFailure(error) : provider === "codex" ? classifyCodexFailure(error) : classifyOllamaFailure(error),
|
|
12293
|
+
message: error instanceof Error ? error.message : String(error)
|
|
12294
|
+
});
|
|
12295
|
+
updateSnapshotOnFailure({ provider, config, error: providerError });
|
|
12296
|
+
lastError = providerError;
|
|
12297
|
+
}
|
|
12298
|
+
}
|
|
12299
|
+
const failure = lastError ?? new ProviderCommandError({
|
|
12300
|
+
provider: selectedProvider,
|
|
12301
|
+
kind: "unknown",
|
|
12302
|
+
message: `${providerLabel(selectedProvider)} failed without a classified provider error.`
|
|
12303
|
+
});
|
|
12304
|
+
const fallbackAttempted = attemptedProviders.length > 1;
|
|
12305
|
+
const summary = failure.kind === "not-configured" ? `${providerLabel(selectedProvider)} is not enabled for this HelixEvo capability.` : fallbackAttempted ? `${providerLabel(selectedProvider)} degraded and no configured fallback succeeded.` : `${providerLabel(selectedProvider)} degraded and no fallback was used.`;
|
|
12306
|
+
recordExecution({
|
|
12307
|
+
config,
|
|
12308
|
+
capability: request.capability,
|
|
12309
|
+
scope,
|
|
12310
|
+
selectionMode,
|
|
12311
|
+
selectedProvider,
|
|
12312
|
+
attemptedProviders,
|
|
12313
|
+
fallbackUsed: false,
|
|
12314
|
+
success: false,
|
|
12315
|
+
summary,
|
|
12316
|
+
nextStep: failure.nextStep,
|
|
12317
|
+
failureKind: failure.kind,
|
|
12318
|
+
warning: failure.warning
|
|
12319
|
+
});
|
|
12320
|
+
throw failure;
|
|
12321
|
+
}
|
|
12322
|
+
async function probeClaudeProvider(config) {
|
|
12323
|
+
const providerConfig = config.llm.providers.claudeCode;
|
|
12324
|
+
const installed = await hasBinary(providerConfig.command);
|
|
12325
|
+
if (!installed) {
|
|
12326
|
+
return {
|
|
12327
|
+
provider: "claude-code",
|
|
12328
|
+
configured: providerConfig.enabled,
|
|
12329
|
+
enabled: providerConfig.enabled,
|
|
12330
|
+
installed: false,
|
|
12331
|
+
available: false,
|
|
12332
|
+
status: "unavailable",
|
|
12333
|
+
model: providerConfig.model,
|
|
12334
|
+
judgeModel: providerConfig.judgeModel,
|
|
12335
|
+
summary: `Claude Code is unavailable because the \`${providerConfig.command}\` CLI is missing.`,
|
|
12336
|
+
nextStep: `Install or restore the \`${providerConfig.command}\` CLI.`,
|
|
12337
|
+
failureKind: "not-installed",
|
|
12338
|
+
notices: [],
|
|
12339
|
+
lastCheckedAt: new Date().toISOString()
|
|
12340
|
+
};
|
|
12341
|
+
}
|
|
12342
|
+
const authStatus = await readClaudeAuthStatus(providerConfig.command, buildClaudeEnv({ dropOauthToken: true }));
|
|
12343
|
+
const runtime = loadLlmRuntimeState();
|
|
12344
|
+
const previous = runtime.providers["claude-code"];
|
|
12345
|
+
const hasHealthyExecution = previous.lastSucceededAt && (!previous.lastFailedAt || previous.lastSucceededAt >= previous.lastFailedAt);
|
|
12346
|
+
const status = hasHealthyExecution ? "healthy" : authStatus?.loggedIn ? "unknown" : "degraded";
|
|
12347
|
+
return {
|
|
12348
|
+
...previous,
|
|
12349
|
+
provider: "claude-code",
|
|
12350
|
+
configured: providerConfig.enabled,
|
|
12351
|
+
enabled: providerConfig.enabled,
|
|
12352
|
+
installed: true,
|
|
12353
|
+
available: Boolean(authStatus?.loggedIn),
|
|
12354
|
+
loggedIn: authStatus?.loggedIn,
|
|
12355
|
+
authMethod: authStatus?.authMethod,
|
|
12356
|
+
apiProvider: authStatus?.apiProvider,
|
|
12357
|
+
status,
|
|
12358
|
+
model: providerConfig.model,
|
|
12359
|
+
judgeModel: providerConfig.judgeModel,
|
|
12360
|
+
summary: hasHealthyExecution ? previous.summary : authStatus?.loggedIn ? "Claude Code is installed and logged in, but no healthy live execution has been recorded yet." : "Claude Code is installed, but login/auth health is not currently confirmed.",
|
|
12361
|
+
nextStep: hasHealthyExecution ? previous.nextStep : authStatus?.loggedIn ? "Run a Claude-backed command to confirm live execution health." : `Run \`${providerConfig.command} auth login\` to refresh Claude auth, then retry.`,
|
|
12362
|
+
lastCheckedAt: new Date().toISOString(),
|
|
12363
|
+
notices: previous.notices ?? []
|
|
12364
|
+
};
|
|
12365
|
+
}
|
|
12366
|
+
async function probeCodexProvider(config) {
|
|
12367
|
+
const providerConfig = config.llm.providers.codex;
|
|
12368
|
+
const installed = await hasBinary(providerConfig.command);
|
|
12369
|
+
const runtime = loadLlmRuntimeState();
|
|
12370
|
+
const previous = runtime.providers.codex;
|
|
12371
|
+
return {
|
|
12372
|
+
...previous,
|
|
12373
|
+
provider: "codex",
|
|
12374
|
+
configured: providerConfig.enabled,
|
|
12375
|
+
enabled: providerConfig.enabled,
|
|
12376
|
+
installed,
|
|
12377
|
+
available: installed && providerConfig.enabled,
|
|
12378
|
+
status: !providerConfig.enabled ? "unknown" : installed ? previous.status === "healthy" || previous.status === "degraded" ? previous.status : "unknown" : "unavailable",
|
|
12379
|
+
model: providerConfig.model,
|
|
12380
|
+
judgeModel: providerConfig.judgeModel,
|
|
12381
|
+
summary: !providerConfig.enabled ? "GPT Codex is configured as an optional provider but currently disabled." : installed ? previous.summary || "GPT Codex is installed. No live Codex execution has been recorded yet." : `GPT Codex is unavailable because the \`${providerConfig.command}\` CLI is missing.`,
|
|
12382
|
+
nextStep: !providerConfig.enabled ? "Enable Codex in ~/.helix/config.json before selecting it." : installed ? previous.nextStep || "Run a Codex-backed command to record live execution health." : `Install or restore the \`${providerConfig.command}\` CLI.`,
|
|
12383
|
+
failureKind: installed ? previous.failureKind : "not-installed",
|
|
12384
|
+
lastCheckedAt: new Date().toISOString(),
|
|
12385
|
+
notices: previous.notices ?? []
|
|
12386
|
+
};
|
|
12387
|
+
}
|
|
12388
|
+
async function probeOllamaProvider(config) {
|
|
12389
|
+
const providerConfig = config.llm.providers.ollama;
|
|
12390
|
+
const runtime = loadLlmRuntimeState();
|
|
12391
|
+
const previous = runtime.providers.ollama;
|
|
12392
|
+
let available = false;
|
|
12393
|
+
let summary = previous.summary || "Ollama has not been probed yet.";
|
|
12394
|
+
let nextStep = previous.nextStep;
|
|
12395
|
+
let failureKind = previous.failureKind;
|
|
12396
|
+
let status = previous.status;
|
|
12397
|
+
const notices = [...previous.notices ?? []];
|
|
12398
|
+
try {
|
|
12399
|
+
const response = await fetch(`${providerConfig.host.replace(/\/$/, "")}/api/tags`, {
|
|
12400
|
+
signal: AbortSignal.timeout(8000)
|
|
12401
|
+
});
|
|
12402
|
+
available = response.ok;
|
|
12403
|
+
status = available ? previous.status === "healthy" || previous.status === "degraded" ? previous.status : "unknown" : "degraded";
|
|
12404
|
+
summary = available ? previous.summary || "Ollama is reachable. No live Ollama execution has been recorded yet." : `Ollama responded from ${providerConfig.host} but did not report a healthy tags endpoint.`;
|
|
12405
|
+
nextStep = available ? previous.nextStep || "Run an Ollama-backed command to record live execution health." : "Check the Ollama daemon and configured host before retrying.";
|
|
12406
|
+
failureKind = available ? previous.failureKind : "network";
|
|
12407
|
+
} catch (error) {
|
|
12408
|
+
available = false;
|
|
12409
|
+
status = providerConfig.enabled ? "degraded" : "unknown";
|
|
12410
|
+
summary = `Ollama is not reachable at ${providerConfig.host}.`;
|
|
12411
|
+
nextStep = "Start the Ollama daemon or correct the configured host, then retry.";
|
|
12412
|
+
failureKind = classifyOllamaFailure(error);
|
|
12413
|
+
notices.push(error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200));
|
|
11641
12414
|
}
|
|
11642
|
-
return
|
|
12415
|
+
return {
|
|
12416
|
+
...previous,
|
|
12417
|
+
provider: "ollama",
|
|
12418
|
+
configured: providerConfig.enabled,
|
|
12419
|
+
enabled: providerConfig.enabled,
|
|
12420
|
+
installed: true,
|
|
12421
|
+
available,
|
|
12422
|
+
status,
|
|
12423
|
+
model: providerConfig.model,
|
|
12424
|
+
judgeModel: providerConfig.judgeModel,
|
|
12425
|
+
summary: !providerConfig.enabled ? "Ollama is configured as an optional provider but currently disabled." : summary,
|
|
12426
|
+
nextStep: !providerConfig.enabled ? "Enable Ollama in ~/.helix/config.json before selecting it." : nextStep,
|
|
12427
|
+
failureKind: !providerConfig.enabled ? undefined : failureKind,
|
|
12428
|
+
lastCheckedAt: new Date().toISOString(),
|
|
12429
|
+
notices
|
|
12430
|
+
};
|
|
12431
|
+
}
|
|
12432
|
+
async function refreshLlmRuntimeState() {
|
|
12433
|
+
const config = loadConfig();
|
|
12434
|
+
const claude = await probeClaudeProvider(config);
|
|
12435
|
+
const codex = await probeCodexProvider(config);
|
|
12436
|
+
const ollama = await probeOllamaProvider(config);
|
|
12437
|
+
return saveRuntimeMutation((state) => {
|
|
12438
|
+
state.defaultProvider = config.llm.defaultProvider;
|
|
12439
|
+
state.fallbackPolicy = config.llm.fallbackPolicy;
|
|
12440
|
+
state.fallbackOrder = [...config.llm.fallbackOrder];
|
|
12441
|
+
state.providers["claude-code"] = claude;
|
|
12442
|
+
state.providers.codex = codex;
|
|
12443
|
+
state.providers.ollama = ollama;
|
|
12444
|
+
});
|
|
12445
|
+
}
|
|
12446
|
+
function formatClaudeCommandFailure(context, error) {
|
|
12447
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12448
|
+
return `${context}
|
|
12449
|
+
${message}`;
|
|
12450
|
+
}
|
|
12451
|
+
async function chat(options) {
|
|
12452
|
+
return runWithProviderControl({
|
|
12453
|
+
...options,
|
|
12454
|
+
capability: "chat",
|
|
12455
|
+
allowFallback: options.allowFallback
|
|
12456
|
+
});
|
|
11643
12457
|
}
|
|
11644
12458
|
async function chatJson(options) {
|
|
11645
12459
|
const enrichedPrompt = options.prompt + `
|
|
11646
12460
|
|
|
11647
12461
|
Respond with valid JSON only. No markdown, no explanation, just the JSON object.`;
|
|
11648
|
-
const text = await
|
|
12462
|
+
const text = await runWithProviderControl({
|
|
12463
|
+
...options,
|
|
12464
|
+
prompt: enrichedPrompt,
|
|
12465
|
+
capability: options.provider ? "json" : options.model ? "judge" : "json",
|
|
12466
|
+
allowFallback: options.allowFallback
|
|
12467
|
+
});
|
|
11649
12468
|
const candidates = [
|
|
11650
12469
|
text.trim(),
|
|
11651
12470
|
text.match(/^```(?:json)?\s*\n([\s\S]*)\n```\s*$/m)?.[1]?.trim(),
|
|
@@ -11665,24 +12484,69 @@ Respond with valid JSON only. No markdown, no explanation, just the JSON object.
|
|
|
11665
12484
|
${text.slice(0, 500)}`);
|
|
11666
12485
|
}
|
|
11667
12486
|
async function searchWeb(query) {
|
|
11668
|
-
|
|
11669
|
-
|
|
11670
|
-
"
|
|
11671
|
-
"
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
"--max-turns",
|
|
11675
|
-
"8"
|
|
11676
|
-
];
|
|
11677
|
-
return runClaude(query, args);
|
|
12487
|
+
return runWithProviderControl({
|
|
12488
|
+
prompt: query,
|
|
12489
|
+
capability: "web-search",
|
|
12490
|
+
provider: "claude-code",
|
|
12491
|
+
allowFallback: false
|
|
12492
|
+
});
|
|
11678
12493
|
}
|
|
11679
|
-
var CLAUDE_AUTH_ERROR_PATTERNS;
|
|
12494
|
+
var ProviderCommandError, PROVIDER_LABELS, PROVIDER_CAPABILITIES, CLAUDE_AUTH_ERROR_PATTERNS, CODEX_AUTH_ERROR_PATTERNS, OLLAMA_MODEL_MISSING_PATTERNS;
|
|
11680
12495
|
var init_llm = __esm(() => {
|
|
11681
12496
|
init_config();
|
|
12497
|
+
init_data();
|
|
12498
|
+
ProviderCommandError = class ProviderCommandError extends Error {
|
|
12499
|
+
provider;
|
|
12500
|
+
kind;
|
|
12501
|
+
nextStep;
|
|
12502
|
+
warning;
|
|
12503
|
+
notices;
|
|
12504
|
+
loggedIn;
|
|
12505
|
+
authMethod;
|
|
12506
|
+
apiProvider;
|
|
12507
|
+
constructor(params) {
|
|
12508
|
+
super(params.message);
|
|
12509
|
+
this.name = "ProviderCommandError";
|
|
12510
|
+
this.provider = params.provider;
|
|
12511
|
+
this.kind = params.kind;
|
|
12512
|
+
this.nextStep = params.nextStep;
|
|
12513
|
+
this.warning = params.warning;
|
|
12514
|
+
this.notices = params.notices ?? [];
|
|
12515
|
+
this.loggedIn = params.loggedIn;
|
|
12516
|
+
this.authMethod = params.authMethod;
|
|
12517
|
+
this.apiProvider = params.apiProvider;
|
|
12518
|
+
}
|
|
12519
|
+
};
|
|
12520
|
+
PROVIDER_LABELS = {
|
|
12521
|
+
"claude-code": "Claude Code",
|
|
12522
|
+
codex: "GPT Codex",
|
|
12523
|
+
ollama: "Ollama"
|
|
12524
|
+
};
|
|
12525
|
+
PROVIDER_CAPABILITIES = {
|
|
12526
|
+
"claude-code": ["chat", "json", "judge", "web-search"],
|
|
12527
|
+
codex: ["chat", "json", "judge"],
|
|
12528
|
+
ollama: ["chat", "json", "judge"]
|
|
12529
|
+
};
|
|
11682
12530
|
CLAUDE_AUTH_ERROR_PATTERNS = [
|
|
11683
12531
|
"OAuth token has expired",
|
|
11684
12532
|
"authentication_error",
|
|
11685
|
-
"Failed to authenticate"
|
|
12533
|
+
"Failed to authenticate",
|
|
12534
|
+
"Invalid authentication credentials",
|
|
12535
|
+
"Not logged in",
|
|
12536
|
+
"Please run /login"
|
|
12537
|
+
];
|
|
12538
|
+
CODEX_AUTH_ERROR_PATTERNS = [
|
|
12539
|
+
"Please run `codex login`",
|
|
12540
|
+
"Please run codex login",
|
|
12541
|
+
"authentication",
|
|
12542
|
+
"unauthorized",
|
|
12543
|
+
"401",
|
|
12544
|
+
"login"
|
|
12545
|
+
];
|
|
12546
|
+
OLLAMA_MODEL_MISSING_PATTERNS = [
|
|
12547
|
+
"model not found",
|
|
12548
|
+
"pull model",
|
|
12549
|
+
"not found, try pulling it first"
|
|
11686
12550
|
];
|
|
11687
12551
|
});
|
|
11688
12552
|
|
|
@@ -11924,9 +12788,9 @@ init_config();
|
|
|
11924
12788
|
init_skills();
|
|
11925
12789
|
init_data();
|
|
11926
12790
|
init_llm();
|
|
11927
|
-
import { join as
|
|
12791
|
+
import { join as join6 } from "node:path";
|
|
11928
12792
|
import { homedir as homedir2 } from "node:os";
|
|
11929
|
-
import { existsSync as
|
|
12793
|
+
import { existsSync as existsSync5, cpSync } from "node:fs";
|
|
11930
12794
|
|
|
11931
12795
|
// src/prompts/test-gen.ts
|
|
11932
12796
|
function buildTestGenPrompt(skill) {
|
|
@@ -11955,10 +12819,10 @@ Return JSON:
|
|
|
11955
12819
|
|
|
11956
12820
|
// src/version.ts
|
|
11957
12821
|
import { createRequire as createRequire2 } from "node:module";
|
|
11958
|
-
import { join as
|
|
12822
|
+
import { join as join5, dirname as dirname2 } from "node:path";
|
|
11959
12823
|
import { fileURLToPath } from "node:url";
|
|
11960
12824
|
var require2 = createRequire2(import.meta.url);
|
|
11961
|
-
var pkg = require2(
|
|
12825
|
+
var pkg = require2(join5(dirname2(fileURLToPath(import.meta.url)), "..", "package.json"));
|
|
11962
12826
|
var VERSION = pkg.version;
|
|
11963
12827
|
|
|
11964
12828
|
// src/commands/init.ts
|
|
@@ -11969,26 +12833,26 @@ async function initCommand(options) {
|
|
|
11969
12833
|
const generalDir = getGeneralSkillsPath();
|
|
11970
12834
|
ensureDir(sgDir);
|
|
11971
12835
|
ensureDir(generalDir);
|
|
11972
|
-
if (!
|
|
12836
|
+
if (!existsSync5(join6(sgDir, "config.json"))) {
|
|
11973
12837
|
saveConfig(DEFAULT_CONFIG);
|
|
11974
12838
|
console.log(" ✓ Created config.json");
|
|
11975
12839
|
}
|
|
11976
12840
|
const defaultPaths = [
|
|
11977
|
-
|
|
11978
|
-
|
|
12841
|
+
join6(homedir2(), ".agents", "skills"),
|
|
12842
|
+
join6(homedir2(), ".craft-agent", "workspaces")
|
|
11979
12843
|
];
|
|
11980
12844
|
const scanPaths = options.skillsPaths ?? defaultPaths;
|
|
11981
12845
|
const expandedPaths = [];
|
|
11982
12846
|
for (const p of scanPaths) {
|
|
11983
|
-
if (p.includes("workspaces") &&
|
|
12847
|
+
if (p.includes("workspaces") && existsSync5(p)) {
|
|
11984
12848
|
const { readdirSync: readdirSync3 } = await import("node:fs");
|
|
11985
12849
|
const workspaces = readdirSync3(p, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("."));
|
|
11986
12850
|
for (const ws of workspaces) {
|
|
11987
|
-
const skillsDir =
|
|
11988
|
-
if (
|
|
12851
|
+
const skillsDir = join6(p, ws.name, "skills");
|
|
12852
|
+
if (existsSync5(skillsDir))
|
|
11989
12853
|
expandedPaths.push(skillsDir);
|
|
11990
12854
|
}
|
|
11991
|
-
} else if (
|
|
12855
|
+
} else if (existsSync5(p)) {
|
|
11992
12856
|
expandedPaths.push(p);
|
|
11993
12857
|
}
|
|
11994
12858
|
}
|
|
@@ -11997,8 +12861,8 @@ async function initCommand(options) {
|
|
|
11997
12861
|
`);
|
|
11998
12862
|
let imported = 0;
|
|
11999
12863
|
for (const skill of existingSkills) {
|
|
12000
|
-
const targetDir =
|
|
12001
|
-
if (
|
|
12864
|
+
const targetDir = join6(generalDir, skill.slug);
|
|
12865
|
+
if (existsSync5(targetDir)) {
|
|
12002
12866
|
console.log(` → ${skill.slug}: already exists, skipping`);
|
|
12003
12867
|
continue;
|
|
12004
12868
|
}
|
|
@@ -12074,14 +12938,14 @@ async function initCommand(options) {
|
|
|
12074
12938
|
// src/commands/capture.ts
|
|
12075
12939
|
init_data();
|
|
12076
12940
|
init_llm();
|
|
12077
|
-
import { readFileSync as
|
|
12941
|
+
import { readFileSync as readFileSync5, existsSync as existsSync6 } from "node:fs";
|
|
12078
12942
|
import { basename as basename2 } from "node:path";
|
|
12079
12943
|
async function captureCommand(sessionPath, options) {
|
|
12080
|
-
if (!
|
|
12944
|
+
if (!existsSync6(sessionPath)) {
|
|
12081
12945
|
console.error(`Session file not found: ${sessionPath}`);
|
|
12082
12946
|
process.exit(1);
|
|
12083
12947
|
}
|
|
12084
|
-
const sessionContent =
|
|
12948
|
+
const sessionContent = readFileSync5(sessionPath, "utf-8");
|
|
12085
12949
|
const sessionId = basename2(sessionPath, ".json");
|
|
12086
12950
|
const maxLength = 50000;
|
|
12087
12951
|
const content = sessionContent.length > maxLength ? sessionContent.slice(-maxLength) : sessionContent;
|
|
@@ -12191,7 +13055,7 @@ init_config();
|
|
|
12191
13055
|
init_data();
|
|
12192
13056
|
init_skills();
|
|
12193
13057
|
init_llm();
|
|
12194
|
-
import { join as
|
|
13058
|
+
import { join as join9 } from "node:path";
|
|
12195
13059
|
|
|
12196
13060
|
// src/prompts/cluster.ts
|
|
12197
13061
|
function buildClusterPrompt(failures, skills, graph) {
|
|
@@ -12601,26 +13465,26 @@ Return JSON: { "score": <number>, "reason": "<one sentence>" }`;
|
|
|
12601
13465
|
|
|
12602
13466
|
// src/core/canary.ts
|
|
12603
13467
|
init_config();
|
|
12604
|
-
import { readFileSync as
|
|
12605
|
-
import { join as
|
|
12606
|
-
var CANARY_DIR =
|
|
12607
|
-
var BACKUPS_DIR =
|
|
13468
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync7, cpSync as cpSync2, rmSync as rmSync2 } from "node:fs";
|
|
13469
|
+
import { join as join7 } from "node:path";
|
|
13470
|
+
var CANARY_DIR = join7(getHelixDir(), "canary");
|
|
13471
|
+
var BACKUPS_DIR = join7(getHelixDir(), "backups");
|
|
12608
13472
|
function getRegistryPath() {
|
|
12609
|
-
return
|
|
13473
|
+
return join7(getHelixDir(), "canary-registry.json");
|
|
12610
13474
|
}
|
|
12611
13475
|
function loadRegistry() {
|
|
12612
13476
|
const path = getRegistryPath();
|
|
12613
|
-
if (!
|
|
13477
|
+
if (!existsSync7(path))
|
|
12614
13478
|
return { entries: [] };
|
|
12615
|
-
return JSON.parse(
|
|
13479
|
+
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
12616
13480
|
}
|
|
12617
13481
|
function saveRegistry(registry) {
|
|
12618
13482
|
writeFileSync4(getRegistryPath(), JSON.stringify(registry, null, 2));
|
|
12619
13483
|
}
|
|
12620
13484
|
function backupSkill(skillPath, slug, version) {
|
|
12621
13485
|
ensureDir(BACKUPS_DIR);
|
|
12622
|
-
const backupPath =
|
|
12623
|
-
if (
|
|
13486
|
+
const backupPath = join7(BACKUPS_DIR, `${slug}_${version}_${Date.now()}`);
|
|
13487
|
+
if (existsSync7(skillPath)) {
|
|
12624
13488
|
cpSync2(skillPath, backupPath, { recursive: true });
|
|
12625
13489
|
}
|
|
12626
13490
|
return backupPath;
|
|
@@ -12701,7 +13565,7 @@ init_config();
|
|
|
12701
13565
|
init_skills();
|
|
12702
13566
|
init_data();
|
|
12703
13567
|
init_llm();
|
|
12704
|
-
import { join as
|
|
13568
|
+
import { join as join8 } from "node:path";
|
|
12705
13569
|
async function autoGeneralize(candidates, verbose = false, dryRun = false) {
|
|
12706
13570
|
const created = [];
|
|
12707
13571
|
const skipped = [];
|
|
@@ -12770,7 +13634,7 @@ Keep it focused and actionable. No filler.`
|
|
|
12770
13634
|
meta.lastEvolved = new Date().toISOString();
|
|
12771
13635
|
meta.tags = [...meta.tags ?? [], "auto-generalized"];
|
|
12772
13636
|
meta.enhances = candidate.sourceSkills;
|
|
12773
|
-
const skillDir =
|
|
13637
|
+
const skillDir = join8(getGeneralSkillsPath(), candidate.suggestedName);
|
|
12774
13638
|
writeSkill(skillDir, meta, content);
|
|
12775
13639
|
created.push(candidate.suggestedName);
|
|
12776
13640
|
console.log(` ✓ Created: ${candidate.suggestedName} (domain layer)`);
|
|
@@ -13038,7 +13902,7 @@ async function evolveCommand(options) {
|
|
|
13038
13902
|
if (finalAccepted && !dryRun) {
|
|
13039
13903
|
const { meta, content } = parseSkillMd(proposalOutput.proposedSkillMd);
|
|
13040
13904
|
const skillSlug2 = proposalOutput.targetSkill ?? proposal.targetSkill;
|
|
13041
|
-
const skillDir =
|
|
13905
|
+
const skillDir = join9(getGeneralSkillsPath(), skillSlug2);
|
|
13042
13906
|
meta.generation = generation;
|
|
13043
13907
|
meta.score = avgScore;
|
|
13044
13908
|
meta.lastEvolved = new Date().toISOString();
|
|
@@ -13159,16 +14023,16 @@ init_skills();
|
|
|
13159
14023
|
|
|
13160
14024
|
// src/core/knowledge-buffer.ts
|
|
13161
14025
|
init_config();
|
|
13162
|
-
import { readFileSync as
|
|
13163
|
-
import { join as
|
|
13164
|
-
var BUFFER_PATH = () =>
|
|
13165
|
-
var DRAFTS_DIR = () =>
|
|
14026
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "node:fs";
|
|
14027
|
+
import { join as join10 } from "node:path";
|
|
14028
|
+
var BUFFER_PATH = () => join10(getHelixDir(), "knowledge-buffer.json");
|
|
14029
|
+
var DRAFTS_DIR = () => join10(getHelixDir(), "drafts");
|
|
13166
14030
|
var MAX_DISCOVERIES = 50;
|
|
13167
14031
|
var MAX_DRAFTS = 10;
|
|
13168
14032
|
var DECAY_RATE = 0.1;
|
|
13169
14033
|
function loadBuffer() {
|
|
13170
14034
|
const path = BUFFER_PATH();
|
|
13171
|
-
if (!
|
|
14035
|
+
if (!existsSync8(path)) {
|
|
13172
14036
|
return {
|
|
13173
14037
|
discoveries: [],
|
|
13174
14038
|
drafts: [],
|
|
@@ -13177,7 +14041,7 @@ function loadBuffer() {
|
|
|
13177
14041
|
decayRate: DECAY_RATE
|
|
13178
14042
|
};
|
|
13179
14043
|
}
|
|
13180
|
-
return JSON.parse(
|
|
14044
|
+
return JSON.parse(readFileSync7(path, "utf-8"));
|
|
13181
14045
|
}
|
|
13182
14046
|
function saveBuffer(buffer) {
|
|
13183
14047
|
writeFileSync5(BUFFER_PATH(), JSON.stringify(buffer, null, 2));
|
|
@@ -13219,9 +14083,9 @@ function saveDraft(draft) {
|
|
|
13219
14083
|
existing.avgScore = draft.avgScore;
|
|
13220
14084
|
existing.iteration++;
|
|
13221
14085
|
existing.lastIterated = new Date().toISOString();
|
|
13222
|
-
const draftDir =
|
|
14086
|
+
const draftDir = join10(DRAFTS_DIR(), draft.skillName);
|
|
13223
14087
|
ensureDir(draftDir);
|
|
13224
|
-
writeFileSync5(
|
|
14088
|
+
writeFileSync5(join10(draftDir, "SKILL.md"), draft.skillMd);
|
|
13225
14089
|
}
|
|
13226
14090
|
} else {
|
|
13227
14091
|
buffer.drafts.push({
|
|
@@ -13235,9 +14099,9 @@ function saveDraft(draft) {
|
|
|
13235
14099
|
iteration: 1,
|
|
13236
14100
|
lastIterated: new Date().toISOString()
|
|
13237
14101
|
});
|
|
13238
|
-
const draftDir =
|
|
14102
|
+
const draftDir = join10(DRAFTS_DIR(), draft.skillName);
|
|
13239
14103
|
ensureDir(draftDir);
|
|
13240
|
-
writeFileSync5(
|
|
14104
|
+
writeFileSync5(join10(draftDir, "SKILL.md"), draft.skillMd);
|
|
13241
14105
|
}
|
|
13242
14106
|
trimAndSave(buffer);
|
|
13243
14107
|
}
|
|
@@ -13285,6 +14149,16 @@ function getBufferStats() {
|
|
|
13285
14149
|
}
|
|
13286
14150
|
|
|
13287
14151
|
// src/commands/status.ts
|
|
14152
|
+
init_llm();
|
|
14153
|
+
function providerStatusBadge(status) {
|
|
14154
|
+
if (status === "healthy")
|
|
14155
|
+
return "healthy";
|
|
14156
|
+
if (status === "degraded")
|
|
14157
|
+
return "degraded";
|
|
14158
|
+
if (status === "unavailable")
|
|
14159
|
+
return "unavailable";
|
|
14160
|
+
return "unknown";
|
|
14161
|
+
}
|
|
13288
14162
|
async function statusCommand() {
|
|
13289
14163
|
const failures = loadFailures();
|
|
13290
14164
|
const unresolved = failures.filter((f) => !f.resolved);
|
|
@@ -13293,6 +14167,7 @@ async function statusCommand() {
|
|
|
13293
14167
|
const skillTests = loadSkillTests();
|
|
13294
14168
|
const stagnation = getStagnationCount();
|
|
13295
14169
|
const recentIter = getRecentIterations(7);
|
|
14170
|
+
const llmRuntime = await refreshLlmRuntimeState();
|
|
13296
14171
|
console.log(`\uD83E\uDDEC HelixEvo Status
|
|
13297
14172
|
`);
|
|
13298
14173
|
console.log(` Skills: ${skills.length}`);
|
|
@@ -13317,6 +14192,29 @@ async function statusCommand() {
|
|
|
13317
14192
|
console.log(` Drafts: ${buffer.drafts}/${buffer.maxDrafts}`);
|
|
13318
14193
|
if (buffer.topDraft)
|
|
13319
14194
|
console.log(` Top draft: ${buffer.topDraft}`);
|
|
14195
|
+
console.log(`
|
|
14196
|
+
Provider control:`);
|
|
14197
|
+
console.log(` Default provider: ${getProviderLabel(llmRuntime.defaultProvider)}`);
|
|
14198
|
+
console.log(` Fallback policy: ${llmRuntime.fallbackPolicy}${llmRuntime.fallbackOrder.length > 0 ? ` (${llmRuntime.fallbackOrder.map((provider) => getProviderLabel(provider)).join(" → ")})` : ""}`);
|
|
14199
|
+
for (const provider of ["claude-code", "codex", "ollama"]) {
|
|
14200
|
+
const snapshot = llmRuntime.providers[provider];
|
|
14201
|
+
const state = providerStatusBadge(snapshot.status);
|
|
14202
|
+
const login = snapshot.loggedIn === undefined ? "" : snapshot.loggedIn ? " • logged in" : " • logged out";
|
|
14203
|
+
const availability = snapshot.available ? "available" : snapshot.installed ? "installed" : "missing";
|
|
14204
|
+
console.log(` ${getProviderLabel(provider)}: ${state} • ${availability}${login}`);
|
|
14205
|
+
console.log(` ${snapshot.summary}`);
|
|
14206
|
+
if (snapshot.nextStep)
|
|
14207
|
+
console.log(` Next: ${snapshot.nextStep}`);
|
|
14208
|
+
}
|
|
14209
|
+
if (llmRuntime.lastExecution) {
|
|
14210
|
+
const execution = llmRuntime.lastExecution;
|
|
14211
|
+
console.log(` Last execution: ${execution.success ? "success" : "failure"} • ${getProviderLabel(execution.selectedProvider)} selected${execution.usedProvider ? ` • ${getProviderLabel(execution.usedProvider)} used` : ""}`);
|
|
14212
|
+
console.log(` ${execution.summary}`);
|
|
14213
|
+
if (execution.nextStep)
|
|
14214
|
+
console.log(` Next: ${execution.nextStep}`);
|
|
14215
|
+
} else {
|
|
14216
|
+
console.log(" Last execution: none recorded yet");
|
|
14217
|
+
}
|
|
13320
14218
|
const accepted = recentIter.flatMap((i) => i.proposals).filter((p) => p.outcome === "accepted").length;
|
|
13321
14219
|
const rejected = recentIter.flatMap((i) => i.proposals).filter((p) => p.outcome === "rejected").length;
|
|
13322
14220
|
console.log(`
|
|
@@ -13338,7 +14236,7 @@ async function statusCommand() {
|
|
|
13338
14236
|
init_data();
|
|
13339
14237
|
init_skills();
|
|
13340
14238
|
import { writeFileSync as writeFileSync6 } from "node:fs";
|
|
13341
|
-
import { join as
|
|
14239
|
+
import { join as join11 } from "node:path";
|
|
13342
14240
|
init_config();
|
|
13343
14241
|
async function reportCommand(options) {
|
|
13344
14242
|
const days = parseInt(options.days ?? "1");
|
|
@@ -13428,8 +14326,8 @@ async function reportCommand(options) {
|
|
|
13428
14326
|
`;
|
|
13429
14327
|
report += `- Run **helixevo proof --status** to inspect outcome attribution across interventions, topology, ontology, and evolution evidence.
|
|
13430
14328
|
`;
|
|
13431
|
-
const outputPath = options.output ??
|
|
13432
|
-
ensureDir(
|
|
14329
|
+
const outputPath = options.output ?? join11(getHelixDir(), "reports", `${date}.md`);
|
|
14330
|
+
ensureDir(join11(getHelixDir(), "reports"));
|
|
13433
14331
|
writeFileSync6(outputPath, report);
|
|
13434
14332
|
console.log(report);
|
|
13435
14333
|
console.log(`
|
|
@@ -13441,7 +14339,7 @@ init_config();
|
|
|
13441
14339
|
init_skills();
|
|
13442
14340
|
init_data();
|
|
13443
14341
|
init_llm();
|
|
13444
|
-
import { join as
|
|
14342
|
+
import { join as join12 } from "node:path";
|
|
13445
14343
|
async function generalizeCommand(options) {
|
|
13446
14344
|
const verbose = options.verbose ?? false;
|
|
13447
14345
|
const dryRun = options.dryRun ?? false;
|
|
@@ -13456,7 +14354,12 @@ async function generalizeCommand(options) {
|
|
|
13456
14354
|
return;
|
|
13457
14355
|
}
|
|
13458
14356
|
console.log(" Step 1: Detecting cross-skill patterns...");
|
|
13459
|
-
|
|
14357
|
+
let candidates;
|
|
14358
|
+
try {
|
|
14359
|
+
candidates = await detectGeneralizationCandidates(skills);
|
|
14360
|
+
} catch (error) {
|
|
14361
|
+
throw new Error(formatClaudeCommandFailure("Generalize could not detect cross-skill patterns.", error));
|
|
14362
|
+
}
|
|
13460
14363
|
if (candidates.length === 0) {
|
|
13461
14364
|
console.log(" No generalization candidates found.");
|
|
13462
14365
|
return;
|
|
@@ -13480,7 +14383,12 @@ async function generalizeCommand(options) {
|
|
|
13480
14383
|
}
|
|
13481
14384
|
console.log(" Generating generalized skill...");
|
|
13482
14385
|
const sourceSkills = skills.filter((s) => candidate.skills.includes(s.slug));
|
|
13483
|
-
|
|
14386
|
+
let generalized;
|
|
14387
|
+
try {
|
|
14388
|
+
generalized = await generateGeneralizedSkill(sourceSkills, candidate);
|
|
14389
|
+
} catch (error) {
|
|
14390
|
+
throw new Error(formatClaudeCommandFailure(`Generalize detected candidate "${candidate.suggestedName}" but could not materialize the generalized skill.`, error));
|
|
14391
|
+
}
|
|
13484
14392
|
if (verbose) {
|
|
13485
14393
|
console.log(` Name: ${generalized.name}`);
|
|
13486
14394
|
console.log(` Content preview: ${generalized.skillMd.slice(0, 200)}...
|
|
@@ -13516,7 +14424,7 @@ async function generalizeCommand(options) {
|
|
|
13516
14424
|
meta.generation = 1;
|
|
13517
14425
|
meta.score = candidate.confidence;
|
|
13518
14426
|
meta.lastEvolved = completedAt;
|
|
13519
|
-
const skillDir =
|
|
14427
|
+
const skillDir = join12(getGeneralSkillsPath(), candidate.suggestedName);
|
|
13520
14428
|
writeSkill(skillDir, meta, content);
|
|
13521
14429
|
console.log(` ✓ Created: ${candidate.suggestedName} (${candidate.suggestedLayer} layer)
|
|
13522
14430
|
`);
|
|
@@ -13687,7 +14595,7 @@ Return JSON:
|
|
|
13687
14595
|
init_data();
|
|
13688
14596
|
init_skills();
|
|
13689
14597
|
init_llm();
|
|
13690
|
-
import { join as
|
|
14598
|
+
import { join as join13 } from "node:path";
|
|
13691
14599
|
async function specializeCommand(options) {
|
|
13692
14600
|
const verbose = options.verbose ?? false;
|
|
13693
14601
|
const dryRun = options.dryRun ?? false;
|
|
@@ -13792,8 +14700,8 @@ async function specializeCommand(options) {
|
|
|
13792
14700
|
meta.generation = 1;
|
|
13793
14701
|
meta.score = candidate.confidence;
|
|
13794
14702
|
meta.lastEvolved = new Date().toISOString();
|
|
13795
|
-
const projectSkillsDir =
|
|
13796
|
-
const skillDir =
|
|
14703
|
+
const projectSkillsDir = join13(process.cwd(), ".helix", "skills");
|
|
14704
|
+
const skillDir = join13(projectSkillsDir, candidate.suggestedName);
|
|
13797
14705
|
writeSkill(skillDir, meta, content);
|
|
13798
14706
|
console.log(` ✓ Created: ${candidate.suggestedName} (project layer, parent: ${candidate.domainSkill})
|
|
13799
14707
|
`);
|
|
@@ -13897,8 +14805,8 @@ Return JSON:
|
|
|
13897
14805
|
// src/commands/graph.ts
|
|
13898
14806
|
import { writeFileSync as writeFileSync8 } from "node:fs";
|
|
13899
14807
|
import { execSync } from "node:child_process";
|
|
13900
|
-
import { join as
|
|
13901
|
-
import { tmpdir } from "node:os";
|
|
14808
|
+
import { join as join15 } from "node:path";
|
|
14809
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
13902
14810
|
|
|
13903
14811
|
// src/core/network.ts
|
|
13904
14812
|
init_ontology();
|
|
@@ -14174,16 +15082,46 @@ async function optimizeNetwork(verbose = false) {
|
|
|
14174
15082
|
const result = {
|
|
14175
15083
|
merged: preview.merged.map((candidate) => candidate.slugs),
|
|
14176
15084
|
split: preview.split.map((candidate) => candidate.slug),
|
|
14177
|
-
conflictsResolved: []
|
|
15085
|
+
conflictsResolved: [],
|
|
15086
|
+
conflictsAttempted: 0,
|
|
15087
|
+
conflictsSkipped: 0,
|
|
15088
|
+
conflictFailures: [],
|
|
15089
|
+
degraded: false
|
|
14178
15090
|
};
|
|
14179
15091
|
const conflicts = graph.edges.filter((edge) => edge.type === "conflicts");
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
|
|
15092
|
+
let authBlockedFurtherConflictAnalysis = false;
|
|
15093
|
+
if (conflicts.length > 0 && verbose) {
|
|
15094
|
+
console.log(` Resolving ${conflicts.length} conflict(s)...`);
|
|
15095
|
+
}
|
|
15096
|
+
for (const conflict of conflicts) {
|
|
15097
|
+
if (authBlockedFurtherConflictAnalysis) {
|
|
15098
|
+
result.conflictsSkipped += 1;
|
|
15099
|
+
continue;
|
|
15100
|
+
}
|
|
15101
|
+
result.conflictsAttempted += 1;
|
|
15102
|
+
try {
|
|
14184
15103
|
const resolved = await resolveConflict(conflict, skills);
|
|
14185
15104
|
if (resolved)
|
|
14186
15105
|
result.conflictsResolved.push([conflict.from, conflict.to]);
|
|
15106
|
+
} catch (error) {
|
|
15107
|
+
const failureKind = classifyClaudeFailure(error);
|
|
15108
|
+
result.degraded = true;
|
|
15109
|
+
result.failureKind = result.failureKind ?? (failureKind === "unknown" ? "llm" : failureKind);
|
|
15110
|
+
result.conflictFailures.push({
|
|
15111
|
+
pair: [conflict.from, conflict.to],
|
|
15112
|
+
kind: failureKind === "unknown" ? "llm" : failureKind,
|
|
15113
|
+
message: error instanceof Error ? error.message : String(error)
|
|
15114
|
+
});
|
|
15115
|
+
if (failureKind === "auth") {
|
|
15116
|
+
authBlockedFurtherConflictAnalysis = true;
|
|
15117
|
+
result.conflictsSkipped += Math.max(0, conflicts.length - result.conflictsAttempted);
|
|
15118
|
+
result.warning = "Conflict enrichment degraded because Claude authentication failed.";
|
|
15119
|
+
} else if (!result.warning) {
|
|
15120
|
+
result.warning = "Conflict enrichment degraded before all conflict analysis completed.";
|
|
15121
|
+
}
|
|
15122
|
+
if (verbose) {
|
|
15123
|
+
console.log(` ! Conflict analysis degraded for ${conflict.from} ↔ ${conflict.to}`);
|
|
15124
|
+
}
|
|
14187
15125
|
}
|
|
14188
15126
|
}
|
|
14189
15127
|
if (verbose) {
|
|
@@ -14238,29 +15176,29 @@ Return JSON:
|
|
|
14238
15176
|
init_data();
|
|
14239
15177
|
init_skills();
|
|
14240
15178
|
import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync3 } from "node:fs";
|
|
14241
|
-
import { join as
|
|
15179
|
+
import { join as join14 } from "node:path";
|
|
14242
15180
|
function syncToObsidian(vaultPath, verbose = false) {
|
|
14243
15181
|
const graph = loadSkillGraph();
|
|
14244
15182
|
const skills = loadAllGeneralSkills();
|
|
14245
|
-
const skillsDir =
|
|
14246
|
-
const reportsDir =
|
|
15183
|
+
const skillsDir = join14(vaultPath, "Skills");
|
|
15184
|
+
const reportsDir = join14(vaultPath, "Reports");
|
|
14247
15185
|
mkdirSync3(skillsDir, { recursive: true });
|
|
14248
15186
|
mkdirSync3(reportsDir, { recursive: true });
|
|
14249
15187
|
for (const node of graph.nodes) {
|
|
14250
15188
|
const skill = skills.find((s) => s.slug === node.id);
|
|
14251
15189
|
const note = generateSkillNote(node, graph.edges, skill ?? undefined);
|
|
14252
|
-
const notePath =
|
|
15190
|
+
const notePath = join14(skillsDir, `${node.id}.md`);
|
|
14253
15191
|
writeFileSync7(notePath, note);
|
|
14254
15192
|
if (verbose)
|
|
14255
15193
|
console.log(` ✓ ${node.id}.md`);
|
|
14256
15194
|
}
|
|
14257
15195
|
const indexNote = generateIndexNote(graph);
|
|
14258
|
-
writeFileSync7(
|
|
15196
|
+
writeFileSync7(join14(vaultPath, "HelixEvo Index.md"), indexNote);
|
|
14259
15197
|
const recent = getRecentIterations(7);
|
|
14260
15198
|
if (recent.length > 0) {
|
|
14261
15199
|
const report = generateEvolutionReport(recent);
|
|
14262
15200
|
const date = new Date().toISOString().slice(0, 10);
|
|
14263
|
-
writeFileSync7(
|
|
15201
|
+
writeFileSync7(join14(reportsDir, `Evolution ${date}.md`), report);
|
|
14264
15202
|
}
|
|
14265
15203
|
console.log(` ✓ Synced ${graph.nodes.length} skills to ${vaultPath}`);
|
|
14266
15204
|
}
|
|
@@ -14412,7 +15350,57 @@ async function graphCommand(options) {
|
|
|
14412
15350
|
console.log(`
|
|
14413
15351
|
${C.bold} Network Optimization${C.reset}`);
|
|
14414
15352
|
console.log(` ${"─".repeat(40)}`);
|
|
14415
|
-
|
|
15353
|
+
let topology;
|
|
15354
|
+
try {
|
|
15355
|
+
topology = refreshTopologyReviewCandidates();
|
|
15356
|
+
} catch (error) {
|
|
15357
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
15358
|
+
saveTopologyOptimizeStatus({
|
|
15359
|
+
command: "graph-optimize",
|
|
15360
|
+
status: "failed",
|
|
15361
|
+
ranAt: new Date().toISOString(),
|
|
15362
|
+
summary: "Topology review queue refresh failed before optimization could complete.",
|
|
15363
|
+
nextStep: "Inspect the optimize error and retry after fixing the queue-refresh failure.",
|
|
15364
|
+
notices: [message],
|
|
15365
|
+
queue: { total: 0, open: 0, created: 0, updated: 0 },
|
|
15366
|
+
enrichment: { attempted: 0, resolved: 0, failed: 0, skipped: 0, degraded: false, failureKind: "unknown", warning: message }
|
|
15367
|
+
});
|
|
15368
|
+
throw new Error(`graph --optimize could not refresh the topology review queue.
|
|
15369
|
+
${message}`);
|
|
15370
|
+
}
|
|
15371
|
+
console.log(` ${C.blue}↺${C.reset} Topology review queue refreshed: ${topology.total} total • ${topology.open} open`);
|
|
15372
|
+
console.log(` ${C.dim} ${topology.created} created • ${topology.updated} updated${C.reset}`);
|
|
15373
|
+
let result;
|
|
15374
|
+
try {
|
|
15375
|
+
result = await optimizeNetwork(verbose);
|
|
15376
|
+
} catch (error) {
|
|
15377
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
15378
|
+
saveTopologyOptimizeStatus({
|
|
15379
|
+
command: "graph-optimize",
|
|
15380
|
+
status: "failed",
|
|
15381
|
+
ranAt: new Date().toISOString(),
|
|
15382
|
+
summary: "Topology review queue refreshed, but optimization failed before enrichment completed.",
|
|
15383
|
+
nextStep: "Review the optimize error, then retry once the underlying execution issue is fixed.",
|
|
15384
|
+
notices: [message],
|
|
15385
|
+
queue: {
|
|
15386
|
+
total: topology.total,
|
|
15387
|
+
open: topology.open,
|
|
15388
|
+
created: topology.created,
|
|
15389
|
+
updated: topology.updated
|
|
15390
|
+
},
|
|
15391
|
+
enrichment: {
|
|
15392
|
+
attempted: 0,
|
|
15393
|
+
resolved: 0,
|
|
15394
|
+
failed: 1,
|
|
15395
|
+
skipped: 0,
|
|
15396
|
+
degraded: true,
|
|
15397
|
+
failureKind: "unknown",
|
|
15398
|
+
warning: message
|
|
15399
|
+
}
|
|
15400
|
+
});
|
|
15401
|
+
throw new Error(`graph --optimize refreshed the queue but failed during enrichment.
|
|
15402
|
+
${message}`);
|
|
15403
|
+
}
|
|
14416
15404
|
if (result.merged.length > 0) {
|
|
14417
15405
|
for (const [a, b] of result.merged)
|
|
14418
15406
|
console.log(` ${C.yellow}⊕${C.reset} Merge: ${a} + ${b}`);
|
|
@@ -14424,12 +15412,42 @@ ${C.bold} Network Optimization${C.reset}`);
|
|
|
14424
15412
|
if (result.conflictsResolved.length > 0) {
|
|
14425
15413
|
console.log(` ${C.green}✓${C.reset} Conflicts analyzed: ${result.conflictsResolved.length}`);
|
|
14426
15414
|
}
|
|
14427
|
-
if (result.merged.length === 0 && result.split.length === 0 && result.conflictsResolved.length === 0) {
|
|
15415
|
+
if (result.merged.length === 0 && result.split.length === 0 && result.conflictsResolved.length === 0 && !result.degraded) {
|
|
14428
15416
|
console.log(` ${C.green}✓${C.reset} Network is healthy`);
|
|
14429
15417
|
}
|
|
14430
|
-
const
|
|
14431
|
-
|
|
14432
|
-
|
|
15418
|
+
const optimizeStatus = {
|
|
15419
|
+
command: "graph-optimize",
|
|
15420
|
+
status: result.degraded ? "partial" : "healthy",
|
|
15421
|
+
ranAt: new Date().toISOString(),
|
|
15422
|
+
summary: result.degraded ? "Topology review queue refreshed, but conflict enrichment only completed partially." : "Topology review queue refreshed and conflict enrichment completed successfully.",
|
|
15423
|
+
nextStep: result.degraded ? "Review the queue in topology control. If the warning mentions authentication, refresh Claude auth before retrying full enrichment." : topology.open > 0 ? "Review open topology candidates and decide which ones to accept, defer, or reject." : "No structural backlog is waiting right now. Re-run optimize after more pressure or graph changes appear.",
|
|
15424
|
+
notices: result.degraded ? [
|
|
15425
|
+
"Deterministic queue refresh succeeded even though enrichment degraded.",
|
|
15426
|
+
result.warning ?? "Conflict enrichment did not complete fully."
|
|
15427
|
+
] : ["Queue refresh and enrichment completed without degraded-mode warnings."],
|
|
15428
|
+
queue: {
|
|
15429
|
+
total: topology.total,
|
|
15430
|
+
open: topology.open,
|
|
15431
|
+
created: topology.created,
|
|
15432
|
+
updated: topology.updated
|
|
15433
|
+
},
|
|
15434
|
+
enrichment: {
|
|
15435
|
+
attempted: result.conflictsAttempted,
|
|
15436
|
+
resolved: result.conflictsResolved.length,
|
|
15437
|
+
failed: result.conflictFailures.length,
|
|
15438
|
+
skipped: result.conflictsSkipped,
|
|
15439
|
+
degraded: result.degraded,
|
|
15440
|
+
failureKind: result.failureKind,
|
|
15441
|
+
warning: result.warning
|
|
15442
|
+
}
|
|
15443
|
+
};
|
|
15444
|
+
saveTopologyOptimizeStatus(optimizeStatus);
|
|
15445
|
+
if (result.degraded) {
|
|
15446
|
+
console.log(` ${C.yellow}!${C.reset} Optimize finished in partial mode`);
|
|
15447
|
+
console.log(` ${C.dim} Queue refresh succeeded, but conflict enrichment degraded.${C.reset}`);
|
|
15448
|
+
if (result.warning)
|
|
15449
|
+
console.log(` ${C.dim} ${result.warning}${C.reset}`);
|
|
15450
|
+
}
|
|
14433
15451
|
}
|
|
14434
15452
|
if (options.obsidian) {
|
|
14435
15453
|
console.log(`
|
|
@@ -14622,7 +15640,7 @@ ${mermaidCode}
|
|
|
14622
15640
|
});
|
|
14623
15641
|
</script>
|
|
14624
15642
|
</body></html>`;
|
|
14625
|
-
const htmlPath =
|
|
15643
|
+
const htmlPath = join15(tmpdir2(), "helix-network.html");
|
|
14626
15644
|
writeFileSync8(htmlPath, html);
|
|
14627
15645
|
execSync(`open "${htmlPath}"`);
|
|
14628
15646
|
console.log(` ✓ Opened in browser`);
|
|
@@ -14687,8 +15705,8 @@ function renderScoreBar(score) {
|
|
|
14687
15705
|
init_config();
|
|
14688
15706
|
init_skills();
|
|
14689
15707
|
init_llm();
|
|
14690
|
-
import { isAbsolute, join as
|
|
14691
|
-
import { readFileSync as
|
|
15708
|
+
import { isAbsolute, join as join16, resolve } from "node:path";
|
|
15709
|
+
import { readFileSync as readFileSync8, existsSync as existsSync10 } from "node:fs";
|
|
14692
15710
|
init_data();
|
|
14693
15711
|
async function researchCommand(options) {
|
|
14694
15712
|
const verbose = options.verbose ?? false;
|
|
@@ -14835,7 +15853,7 @@ Only include discoveries that are NOT already covered by current skills.`
|
|
|
14835
15853
|
meta.score = result.avgScore / 10;
|
|
14836
15854
|
meta.lastEvolved = new Date().toISOString();
|
|
14837
15855
|
meta.tags = [...meta.tags ?? [], "research-discovered"];
|
|
14838
|
-
const skillDir =
|
|
15856
|
+
const skillDir = join16(getGeneralSkillsPath(), hypothesis.skillName);
|
|
14839
15857
|
writeSkill(skillDir, meta, content);
|
|
14840
15858
|
console.log(` ✓ Created: ${hypothesis.skillName} (from research)
|
|
14841
15859
|
`);
|
|
@@ -14891,9 +15909,9 @@ async function understandGoals(projectPath, skills) {
|
|
|
14891
15909
|
const paths = projectPath ? [projectPath] : [process.cwd()];
|
|
14892
15910
|
for (const p of paths) {
|
|
14893
15911
|
for (const file of ["README.md", "CLAUDE.md", "package.json", ".agents/AGENTS.md"]) {
|
|
14894
|
-
const fullPath =
|
|
14895
|
-
if (
|
|
14896
|
-
const content =
|
|
15912
|
+
const fullPath = join16(p, file);
|
|
15913
|
+
if (existsSync10(fullPath)) {
|
|
15914
|
+
const content = readFileSync8(fullPath, "utf-8").slice(0, 2000);
|
|
14897
15915
|
context.push(`## ${file}
|
|
14898
15916
|
${content}`);
|
|
14899
15917
|
}
|
|
@@ -15053,8 +16071,8 @@ ${replay.slice(0, 800)}`
|
|
|
15053
16071
|
|
|
15054
16072
|
// src/commands/dashboard.ts
|
|
15055
16073
|
import { execSync as execSync2, spawn as spawn2 } from "node:child_process";
|
|
15056
|
-
import { join as
|
|
15057
|
-
import { existsSync as
|
|
16074
|
+
import { join as join18, dirname as dirname3 } from "node:path";
|
|
16075
|
+
import { existsSync as existsSync12, cpSync as cpSync3, mkdirSync as mkdirSync5, openSync, readdirSync as readdirSync3, readFileSync as readFileSync10, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
15058
16076
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
15059
16077
|
import { homedir as homedir4 } from "node:os";
|
|
15060
16078
|
import { createServer } from "node:net";
|
|
@@ -15062,25 +16080,25 @@ init_skills();
|
|
|
15062
16080
|
init_data();
|
|
15063
16081
|
|
|
15064
16082
|
// src/utils/update-check.ts
|
|
15065
|
-
import { readFileSync as
|
|
15066
|
-
import { join as
|
|
16083
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync11, mkdirSync as mkdirSync4 } from "node:fs";
|
|
16084
|
+
import { join as join17 } from "node:path";
|
|
15067
16085
|
import { homedir as homedir3 } from "node:os";
|
|
15068
|
-
var HELIX_DIR2 =
|
|
15069
|
-
var CACHE_PATH =
|
|
16086
|
+
var HELIX_DIR2 = join17(homedir3(), ".helix");
|
|
16087
|
+
var CACHE_PATH = join17(HELIX_DIR2, "update-check.json");
|
|
15070
16088
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
15071
16089
|
var REGISTRY_URL = "https://registry.npmjs.org/helixevo/latest";
|
|
15072
16090
|
function readCache() {
|
|
15073
16091
|
try {
|
|
15074
|
-
if (!
|
|
16092
|
+
if (!existsSync11(CACHE_PATH))
|
|
15075
16093
|
return null;
|
|
15076
|
-
return JSON.parse(
|
|
16094
|
+
return JSON.parse(readFileSync9(CACHE_PATH, "utf-8"));
|
|
15077
16095
|
} catch {
|
|
15078
16096
|
return null;
|
|
15079
16097
|
}
|
|
15080
16098
|
}
|
|
15081
16099
|
function writeCache(cache) {
|
|
15082
16100
|
try {
|
|
15083
|
-
if (!
|
|
16101
|
+
if (!existsSync11(HELIX_DIR2))
|
|
15084
16102
|
mkdirSync4(HELIX_DIR2, { recursive: true });
|
|
15085
16103
|
writeFileSync9(CACHE_PATH, JSON.stringify(cache));
|
|
15086
16104
|
} catch {}
|
|
@@ -15166,11 +16184,11 @@ function printUpdateBanner(currentVersion, latestVersion) {
|
|
|
15166
16184
|
|
|
15167
16185
|
// src/commands/dashboard.ts
|
|
15168
16186
|
var __filename = "/Users/tianchichen/Documents/GitHub/helixevo/src/commands/dashboard.ts";
|
|
15169
|
-
var HELIX_HOME_DIR =
|
|
15170
|
-
var HELIX_DASHBOARD_DIR =
|
|
15171
|
-
var DASHBOARD_LOG_FILE =
|
|
15172
|
-
var DASHBOARD_PID_FILE =
|
|
15173
|
-
var DASHBOARD_STATE_FILE =
|
|
16187
|
+
var HELIX_HOME_DIR = join18(homedir4(), ".helix");
|
|
16188
|
+
var HELIX_DASHBOARD_DIR = join18(HELIX_HOME_DIR, "dashboard");
|
|
16189
|
+
var DASHBOARD_LOG_FILE = join18(HELIX_HOME_DIR, "dashboard.log");
|
|
16190
|
+
var DASHBOARD_PID_FILE = join18(HELIX_HOME_DIR, "dashboard.pid");
|
|
16191
|
+
var DASHBOARD_STATE_FILE = join18(HELIX_HOME_DIR, "dashboard-state.json");
|
|
15174
16192
|
var DASHBOARD_SKIP_AUTO_UPDATE_ENV = "HELIXEVO_DASHBOARD_SKIP_AUTO_UPDATE";
|
|
15175
16193
|
var DEFAULT_DASHBOARD_PORT = 3847;
|
|
15176
16194
|
var MAX_PORT_SCAN_ATTEMPTS = 25;
|
|
@@ -15195,7 +16213,7 @@ async function dashboardCommand(options) {
|
|
|
15195
16213
|
console.error(` cd helixevo/dashboard && npm install && npx next dev --port ${preferredPort}`);
|
|
15196
16214
|
process.exit(1);
|
|
15197
16215
|
}
|
|
15198
|
-
if (!
|
|
16216
|
+
if (!existsSync12(join18(dir, "node_modules"))) {
|
|
15199
16217
|
console.log(" Installing dashboard dependencies...");
|
|
15200
16218
|
try {
|
|
15201
16219
|
execSync2("npm install --no-audit --no-fund", { cwd: dir, stdio: "inherit" });
|
|
@@ -15328,10 +16346,10 @@ function launchDashboard(dir, openBrowser, port) {
|
|
|
15328
16346
|
\uD83D\uDD04 Restarting dashboard after update on ${getDashboardUrl(port)}...
|
|
15329
16347
|
`);
|
|
15330
16348
|
setTimeout(() => {
|
|
15331
|
-
const nextCache =
|
|
15332
|
-
if (
|
|
16349
|
+
const nextCache = join18(dir, ".next");
|
|
16350
|
+
if (existsSync12(nextCache)) {
|
|
15333
16351
|
try {
|
|
15334
|
-
|
|
16352
|
+
rmSync3(nextCache, { recursive: true });
|
|
15335
16353
|
} catch {}
|
|
15336
16354
|
}
|
|
15337
16355
|
ensureSkillGraph();
|
|
@@ -15402,17 +16420,17 @@ function writeDashboardState(state) {
|
|
|
15402
16420
|
}
|
|
15403
16421
|
function readDashboardState() {
|
|
15404
16422
|
let state = null;
|
|
15405
|
-
if (
|
|
16423
|
+
if (existsSync12(DASHBOARD_STATE_FILE)) {
|
|
15406
16424
|
try {
|
|
15407
|
-
const parsed = JSON.parse(
|
|
16425
|
+
const parsed = JSON.parse(readFileSync10(DASHBOARD_STATE_FILE, "utf-8"));
|
|
15408
16426
|
if (Number.isInteger(parsed.pid) && Number.isInteger(parsed.port)) {
|
|
15409
16427
|
state = parsed;
|
|
15410
16428
|
}
|
|
15411
16429
|
} catch {}
|
|
15412
16430
|
}
|
|
15413
|
-
if (!state &&
|
|
16431
|
+
if (!state && existsSync12(DASHBOARD_PID_FILE)) {
|
|
15414
16432
|
try {
|
|
15415
|
-
const pid = Number.parseInt(
|
|
16433
|
+
const pid = Number.parseInt(readFileSync10(DASHBOARD_PID_FILE, "utf-8").trim(), 10);
|
|
15416
16434
|
if (Number.isInteger(pid)) {
|
|
15417
16435
|
state = {
|
|
15418
16436
|
pid,
|
|
@@ -15433,10 +16451,10 @@ function readDashboardState() {
|
|
|
15433
16451
|
}
|
|
15434
16452
|
function clearDashboardState() {
|
|
15435
16453
|
try {
|
|
15436
|
-
|
|
16454
|
+
rmSync3(DASHBOARD_PID_FILE, { force: true });
|
|
15437
16455
|
} catch {}
|
|
15438
16456
|
try {
|
|
15439
|
-
|
|
16457
|
+
rmSync3(DASHBOARD_STATE_FILE, { force: true });
|
|
15440
16458
|
} catch {}
|
|
15441
16459
|
}
|
|
15442
16460
|
function isProcessAlive(pid) {
|
|
@@ -15474,20 +16492,20 @@ function prepareDashboard() {
|
|
|
15474
16492
|
const npmSource = findNpmDashboard();
|
|
15475
16493
|
if (npmSource)
|
|
15476
16494
|
return copyToHelix(npmSource);
|
|
15477
|
-
if (
|
|
16495
|
+
if (existsSync12(join18(HELIX_DASHBOARD_DIR, "package.json"))) {
|
|
15478
16496
|
return HELIX_DASHBOARD_DIR;
|
|
15479
16497
|
}
|
|
15480
16498
|
return null;
|
|
15481
16499
|
}
|
|
15482
16500
|
function findDevDashboard() {
|
|
15483
16501
|
const candidates = [
|
|
15484
|
-
|
|
16502
|
+
join18(process.cwd(), "dashboard")
|
|
15485
16503
|
];
|
|
15486
16504
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
15487
|
-
candidates.push(
|
|
15488
|
-
candidates.push(
|
|
16505
|
+
candidates.push(join18(home, "Documents", "GitHub", "helixevo", "dashboard"));
|
|
16506
|
+
candidates.push(join18(home, "Documents", "GitHub", "skillgraph", "dashboard"));
|
|
15489
16507
|
for (const dir of candidates) {
|
|
15490
|
-
if (
|
|
16508
|
+
if (existsSync12(join18(dir, "package.json")) && !dir.includes("node_modules")) {
|
|
15491
16509
|
return dir;
|
|
15492
16510
|
}
|
|
15493
16511
|
}
|
|
@@ -15498,15 +16516,15 @@ function findNpmDashboard() {
|
|
|
15498
16516
|
try {
|
|
15499
16517
|
const thisFile = typeof __filename !== "undefined" ? __filename : fileURLToPath2(import.meta.url);
|
|
15500
16518
|
const pkgRoot = dirname3(dirname3(thisFile));
|
|
15501
|
-
candidates.push(
|
|
16519
|
+
candidates.push(join18(pkgRoot, "dashboard"));
|
|
15502
16520
|
} catch {}
|
|
15503
16521
|
try {
|
|
15504
16522
|
const globalPrefix = execSync2("npm prefix -g", { encoding: "utf-8" }).trim();
|
|
15505
|
-
candidates.push(
|
|
15506
|
-
candidates.push(
|
|
16523
|
+
candidates.push(join18(globalPrefix, "lib", "node_modules", "helixevo", "dashboard"));
|
|
16524
|
+
candidates.push(join18(globalPrefix, "node_modules", "helixevo", "dashboard"));
|
|
15507
16525
|
} catch {}
|
|
15508
16526
|
for (const dir of candidates) {
|
|
15509
|
-
if (
|
|
16527
|
+
if (existsSync12(join18(dir, "package.json"))) {
|
|
15510
16528
|
return dir;
|
|
15511
16529
|
}
|
|
15512
16530
|
}
|
|
@@ -15514,10 +16532,10 @@ function findNpmDashboard() {
|
|
|
15514
16532
|
}
|
|
15515
16533
|
function getInstalledDashboardVersion() {
|
|
15516
16534
|
try {
|
|
15517
|
-
const versionFile =
|
|
15518
|
-
if (!
|
|
16535
|
+
const versionFile = join18(HELIX_DASHBOARD_DIR, ".helixevo-version");
|
|
16536
|
+
if (!existsSync12(versionFile))
|
|
15519
16537
|
return null;
|
|
15520
|
-
return
|
|
16538
|
+
return readFileSync10(versionFile, "utf-8").trim();
|
|
15521
16539
|
} catch {
|
|
15522
16540
|
return null;
|
|
15523
16541
|
}
|
|
@@ -15525,8 +16543,8 @@ function getInstalledDashboardVersion() {
|
|
|
15525
16543
|
function copyToHelix(sourceDir) {
|
|
15526
16544
|
let sourceVersion = VERSION;
|
|
15527
16545
|
try {
|
|
15528
|
-
const srcRoot =
|
|
15529
|
-
const pkg2 = JSON.parse(
|
|
16546
|
+
const srcRoot = join18(sourceDir, "..", "package.json");
|
|
16547
|
+
const pkg2 = JSON.parse(readFileSync10(srcRoot, "utf-8"));
|
|
15530
16548
|
if (pkg2.name === "helixevo" && pkg2.version)
|
|
15531
16549
|
sourceVersion = pkg2.version;
|
|
15532
16550
|
} catch {}
|
|
@@ -15542,8 +16560,8 @@ function copyToHelix(sourceDir) {
|
|
|
15542
16560
|
mkdirSync5(HELIX_DASHBOARD_DIR, { recursive: true });
|
|
15543
16561
|
let depsChanged = true;
|
|
15544
16562
|
try {
|
|
15545
|
-
const srcDeps =
|
|
15546
|
-
const dstDeps =
|
|
16563
|
+
const srcDeps = readFileSync10(join18(sourceDir, "package.json"), "utf-8");
|
|
16564
|
+
const dstDeps = readFileSync10(join18(HELIX_DASHBOARD_DIR, "package.json"), "utf-8");
|
|
15547
16565
|
const srcPkg = JSON.parse(srcDeps);
|
|
15548
16566
|
const dstPkg = JSON.parse(dstDeps);
|
|
15549
16567
|
depsChanged = JSON.stringify(srcPkg.dependencies) !== JSON.stringify(dstPkg.dependencies) || JSON.stringify(srcPkg.devDependencies) !== JSON.stringify(dstPkg.devDependencies);
|
|
@@ -15554,22 +16572,22 @@ function copyToHelix(sourceDir) {
|
|
|
15554
16572
|
for (const item of items) {
|
|
15555
16573
|
if (item.name === "node_modules" || item.name === ".next" || item.name === "package-lock.json" || item.name === ".env.local")
|
|
15556
16574
|
continue;
|
|
15557
|
-
const src =
|
|
15558
|
-
const dest =
|
|
16575
|
+
const src = join18(sourceDir, item.name);
|
|
16576
|
+
const dest = join18(HELIX_DASHBOARD_DIR, item.name);
|
|
15559
16577
|
cpSync3(src, dest, { recursive: true });
|
|
15560
16578
|
}
|
|
15561
16579
|
if (depsChanged) {
|
|
15562
|
-
const oldModules =
|
|
15563
|
-
if (
|
|
15564
|
-
|
|
15565
|
-
const oldLock =
|
|
15566
|
-
if (
|
|
15567
|
-
|
|
15568
|
-
}
|
|
15569
|
-
const nextCache =
|
|
15570
|
-
if (
|
|
15571
|
-
|
|
15572
|
-
writeFileSync10(
|
|
16580
|
+
const oldModules = join18(HELIX_DASHBOARD_DIR, "node_modules");
|
|
16581
|
+
if (existsSync12(oldModules))
|
|
16582
|
+
rmSync3(oldModules, { recursive: true });
|
|
16583
|
+
const oldLock = join18(HELIX_DASHBOARD_DIR, "package-lock.json");
|
|
16584
|
+
if (existsSync12(oldLock))
|
|
16585
|
+
rmSync3(oldLock);
|
|
16586
|
+
}
|
|
16587
|
+
const nextCache = join18(HELIX_DASHBOARD_DIR, ".next");
|
|
16588
|
+
if (existsSync12(nextCache))
|
|
16589
|
+
rmSync3(nextCache, { recursive: true });
|
|
16590
|
+
writeFileSync10(join18(HELIX_DASHBOARD_DIR, ".helixevo-version"), sourceVersion);
|
|
15573
16591
|
return HELIX_DASHBOARD_DIR;
|
|
15574
16592
|
}
|
|
15575
16593
|
function ensureSkillGraph() {
|
|
@@ -15611,13 +16629,13 @@ function ensureSkillGraph() {
|
|
|
15611
16629
|
}
|
|
15612
16630
|
|
|
15613
16631
|
// src/commands/watch.ts
|
|
15614
|
-
import { join as
|
|
15615
|
-
import { existsSync as
|
|
16632
|
+
import { join as join20 } from "node:path";
|
|
16633
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
15616
16634
|
|
|
15617
16635
|
// src/core/auto-capture.ts
|
|
15618
16636
|
init_data();
|
|
15619
16637
|
init_llm();
|
|
15620
|
-
import { readFileSync as
|
|
16638
|
+
import { readFileSync as readFileSync11, existsSync as existsSync13, statSync as statSync2 } from "node:fs";
|
|
15621
16639
|
var CORRECTION_SIGNALS = [
|
|
15622
16640
|
/\bno[, ]+(?:that's|thats|that is)\s+(?:wrong|incorrect|not right)/i,
|
|
15623
16641
|
/\bdon'?t\s+do\s+(?:that|it\s+like\s+that)/i,
|
|
@@ -15705,17 +16723,17 @@ If no corrections found, return: { "corrections": [] }`
|
|
|
15705
16723
|
}
|
|
15706
16724
|
function watchEvents(options) {
|
|
15707
16725
|
const { eventsPath, project, onCapture, onError, pollIntervalMs = 5000 } = options;
|
|
15708
|
-
let lastSize =
|
|
16726
|
+
let lastSize = existsSync13(eventsPath) ? statSync2(eventsPath).size : 0;
|
|
15709
16727
|
let messageBuffer = [];
|
|
15710
16728
|
let pendingExtraction = false;
|
|
15711
16729
|
const interval = setInterval(async () => {
|
|
15712
16730
|
try {
|
|
15713
|
-
if (!
|
|
16731
|
+
if (!existsSync13(eventsPath))
|
|
15714
16732
|
return;
|
|
15715
16733
|
const currentSize = statSync2(eventsPath).size;
|
|
15716
16734
|
if (currentSize <= lastSize)
|
|
15717
16735
|
return;
|
|
15718
|
-
const content =
|
|
16736
|
+
const content = readFileSync11(eventsPath, "utf-8");
|
|
15719
16737
|
const lines = content.trim().split(`
|
|
15720
16738
|
`).filter(Boolean);
|
|
15721
16739
|
const newLines = lines.slice(messageBuffer.length);
|
|
@@ -15757,17 +16775,17 @@ init_data();
|
|
|
15757
16775
|
// src/core/metrics.ts
|
|
15758
16776
|
init_config();
|
|
15759
16777
|
init_data();
|
|
15760
|
-
import { readFileSync as
|
|
15761
|
-
import { join as
|
|
16778
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync11, existsSync as existsSync14 } from "node:fs";
|
|
16779
|
+
import { join as join19 } from "node:path";
|
|
15762
16780
|
function getMetricsPath() {
|
|
15763
|
-
return
|
|
16781
|
+
return join19(getHelixDir(), "metrics.json");
|
|
15764
16782
|
}
|
|
15765
16783
|
function loadMetrics() {
|
|
15766
16784
|
const path = getMetricsPath();
|
|
15767
|
-
if (!
|
|
16785
|
+
if (!existsSync14(path)) {
|
|
15768
16786
|
return { updated: new Date().toISOString(), skills: [], globalCorrectionRate: [], evolutionImpact: [] };
|
|
15769
16787
|
}
|
|
15770
|
-
return JSON.parse(
|
|
16788
|
+
return JSON.parse(readFileSync12(path, "utf-8"));
|
|
15771
16789
|
}
|
|
15772
16790
|
function saveMetrics(store) {
|
|
15773
16791
|
ensureDir(getHelixDir());
|
|
@@ -15947,14 +16965,14 @@ async function watchCommand(options) {
|
|
|
15947
16965
|
const verbose = options.verbose ?? false;
|
|
15948
16966
|
const autoEvolve = options.evolve !== false;
|
|
15949
16967
|
const project = options.project ?? null;
|
|
15950
|
-
const eventsPath = options.events ??
|
|
16968
|
+
const eventsPath = options.events ?? join20(process.cwd(), "events.jsonl");
|
|
15951
16969
|
console.log(`\uD83E\uDDEC HelixEvo Watch Mode — Always-On Learning
|
|
15952
16970
|
`);
|
|
15953
16971
|
console.log(` Events: ${eventsPath}`);
|
|
15954
16972
|
console.log(` Project: ${project ?? "(auto-detect)"}`);
|
|
15955
16973
|
console.log(` Auto-evolve: ${autoEvolve ? "ON" : "OFF"}`);
|
|
15956
16974
|
console.log();
|
|
15957
|
-
if (!
|
|
16975
|
+
if (!existsSync15(eventsPath)) {
|
|
15958
16976
|
console.log(` ⚠ Events file not found yet. Will start watching when it appears.`);
|
|
15959
16977
|
console.log(` Expected at: ${eventsPath}
|
|
15960
16978
|
`);
|
|
@@ -16107,8 +17125,8 @@ async function metricsCommand(options) {
|
|
|
16107
17125
|
init_data();
|
|
16108
17126
|
init_skills();
|
|
16109
17127
|
init_llm();
|
|
16110
|
-
import { isAbsolute as isAbsolute2, join as
|
|
16111
|
-
import { existsSync as
|
|
17128
|
+
import { isAbsolute as isAbsolute2, join as join21, normalize, resolve as resolve2 } from "node:path";
|
|
17129
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13, writeFileSync as writeFileSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
16112
17130
|
import { homedir as homedir5 } from "node:os";
|
|
16113
17131
|
|
|
16114
17132
|
// src/prompts/project-analysis.ts
|
|
@@ -16182,7 +17200,7 @@ function expandHomePath(path) {
|
|
|
16182
17200
|
if (path === "~")
|
|
16183
17201
|
return homedir5();
|
|
16184
17202
|
if (path.startsWith("~/"))
|
|
16185
|
-
return
|
|
17203
|
+
return join21(homedir5(), path.slice(2));
|
|
16186
17204
|
return path;
|
|
16187
17205
|
}
|
|
16188
17206
|
function normalizeProjectPath(projectPath) {
|
|
@@ -16207,10 +17225,10 @@ function readProjectContext(projectPath) {
|
|
|
16207
17225
|
"Dockerfile"
|
|
16208
17226
|
];
|
|
16209
17227
|
for (const f of filesToRead) {
|
|
16210
|
-
const p =
|
|
16211
|
-
if (
|
|
17228
|
+
const p = join21(projectPath, f);
|
|
17229
|
+
if (existsSync16(p)) {
|
|
16212
17230
|
try {
|
|
16213
|
-
const content =
|
|
17231
|
+
const content = readFileSync13(p, "utf-8");
|
|
16214
17232
|
contextFiles.push({ name: f, content: content.slice(0, 2000) });
|
|
16215
17233
|
} catch {}
|
|
16216
17234
|
}
|
|
@@ -16222,8 +17240,8 @@ function readProjectContext(projectPath) {
|
|
|
16222
17240
|
`);
|
|
16223
17241
|
} catch {}
|
|
16224
17242
|
let srcStructure = "";
|
|
16225
|
-
const srcDir =
|
|
16226
|
-
if (
|
|
17243
|
+
const srcDir = join21(projectPath, "src");
|
|
17244
|
+
if (existsSync16(srcDir)) {
|
|
16227
17245
|
try {
|
|
16228
17246
|
const scanDir = (dir, prefix, depth) => {
|
|
16229
17247
|
if (depth > 2)
|
|
@@ -16233,7 +17251,7 @@ function readProjectContext(projectPath) {
|
|
|
16233
17251
|
for (const item of items) {
|
|
16234
17252
|
lines.push(`${prefix}${item.isDirectory() ? "\uD83D\uDCC1" : "\uD83D\uDCC4"} ${item.name}`);
|
|
16235
17253
|
if (item.isDirectory()) {
|
|
16236
|
-
lines.push(...scanDir(
|
|
17254
|
+
lines.push(...scanDir(join21(dir, item.name), prefix + " ", depth + 1));
|
|
16237
17255
|
}
|
|
16238
17256
|
}
|
|
16239
17257
|
return lines;
|
|
@@ -16267,18 +17285,18 @@ ${f.content}
|
|
|
16267
17285
|
return context;
|
|
16268
17286
|
}
|
|
16269
17287
|
function getProjectsDir() {
|
|
16270
|
-
return
|
|
17288
|
+
return join21(getHelixDir(), "projects");
|
|
16271
17289
|
}
|
|
16272
17290
|
function saveProjectProfile(profile) {
|
|
16273
|
-
const dir =
|
|
17291
|
+
const dir = join21(getProjectsDir(), profile.name);
|
|
16274
17292
|
mkdirSync6(dir, { recursive: true });
|
|
16275
|
-
writeFileSync12(
|
|
17293
|
+
writeFileSync12(join21(dir, "profile.json"), JSON.stringify(profile, null, 2));
|
|
16276
17294
|
}
|
|
16277
17295
|
async function projectSetupCommand(projectPath, options) {
|
|
16278
17296
|
const verbose = options.verbose ?? false;
|
|
16279
17297
|
const dryRun = options.dryRun ?? false;
|
|
16280
17298
|
const resolvedPath = normalizeProjectPath(projectPath);
|
|
16281
|
-
if (!
|
|
17299
|
+
if (!existsSync16(resolvedPath)) {
|
|
16282
17300
|
console.error(` ✗ Path not found: ${resolvedPath}`);
|
|
16283
17301
|
process.exit(1);
|
|
16284
17302
|
}
|
|
@@ -16408,8 +17426,8 @@ init_data();
|
|
|
16408
17426
|
// src/core/topology-apply.ts
|
|
16409
17427
|
init_config();
|
|
16410
17428
|
init_data();
|
|
16411
|
-
import { copyFileSync, existsSync as
|
|
16412
|
-
import { join as
|
|
17429
|
+
import { copyFileSync, existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync13 } from "node:fs";
|
|
17430
|
+
import { join as join22 } from "node:path";
|
|
16413
17431
|
function uniqueStrings2(values) {
|
|
16414
17432
|
return [...new Set(values.filter((value) => Boolean(value && value.trim())))];
|
|
16415
17433
|
}
|
|
@@ -16435,24 +17453,24 @@ function snapshotId(prefix) {
|
|
|
16435
17453
|
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`.replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
16436
17454
|
}
|
|
16437
17455
|
function readSkillRaw(slug) {
|
|
16438
|
-
const path =
|
|
16439
|
-
if (!
|
|
17456
|
+
const path = join22(getGeneralSkillsPath(), slug, "SKILL.md");
|
|
17457
|
+
if (!existsSync17(path))
|
|
16440
17458
|
return null;
|
|
16441
|
-
return { path, raw:
|
|
17459
|
+
return { path, raw: readFileSync14(path, "utf-8") };
|
|
16442
17460
|
}
|
|
16443
17461
|
function createSnapshot(params) {
|
|
16444
17462
|
const id = snapshotId("topology_snapshot");
|
|
16445
|
-
const snapshotDir =
|
|
17463
|
+
const snapshotDir = join22(getHelixDir(), "topology-snapshots", id);
|
|
16446
17464
|
mkdirSync7(snapshotDir, { recursive: true });
|
|
16447
|
-
const graphPath =
|
|
16448
|
-
const overridesPath =
|
|
17465
|
+
const graphPath = join22(snapshotDir, "skill-graph.json");
|
|
17466
|
+
const overridesPath = join22(snapshotDir, "topology-overrides.json");
|
|
16449
17467
|
writeFileSync13(graphPath, JSON.stringify(loadSkillGraph(), null, 2));
|
|
16450
17468
|
writeFileSync13(overridesPath, JSON.stringify(loadTopologyOverrides(), null, 2));
|
|
16451
17469
|
const skillFileSnapshots = uniqueStrings2(params.skillSlugs ?? []).flatMap((slug) => {
|
|
16452
17470
|
const skill = readSkillRaw(slug);
|
|
16453
17471
|
if (!skill)
|
|
16454
17472
|
return [];
|
|
16455
|
-
const snapshotPath =
|
|
17473
|
+
const snapshotPath = join22(snapshotDir, `${slug}.SKILL.md`);
|
|
16456
17474
|
copyFileSync(skill.path, snapshotPath);
|
|
16457
17475
|
return [{ slug, path: skill.path, snapshotPath }];
|
|
16458
17476
|
});
|
|
@@ -16693,10 +17711,10 @@ function rollbackAppliedTopologyPlan(planId) {
|
|
|
16693
17711
|
throw new Error(`Topology plan ${planId} must be applied before rollback`);
|
|
16694
17712
|
}
|
|
16695
17713
|
const beforeSnapshot = loadTopologySnapshots().find((snapshot) => snapshot.id === plan.beforeSnapshotId);
|
|
16696
|
-
if (!beforeSnapshot || !
|
|
17714
|
+
if (!beforeSnapshot || !existsSync17(beforeSnapshot.overridesPath)) {
|
|
16697
17715
|
throw new Error(`Before-apply snapshot for ${planId} is missing or incomplete`);
|
|
16698
17716
|
}
|
|
16699
|
-
const restoredOverrides = JSON.parse(
|
|
17717
|
+
const restoredOverrides = JSON.parse(readFileSync14(beforeSnapshot.overridesPath, "utf-8"));
|
|
16700
17718
|
saveTopologyOverrides({
|
|
16701
17719
|
...restoredOverrides,
|
|
16702
17720
|
updatedAt: new Date().toISOString()
|
|
@@ -16972,14 +17990,14 @@ async function ontologyCommand(options) {
|
|
|
16972
17990
|
}
|
|
16973
17991
|
|
|
16974
17992
|
// src/core/proof.ts
|
|
16975
|
-
import { existsSync as
|
|
16976
|
-
import { join as
|
|
17993
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
|
|
17994
|
+
import { join as join23 } from "node:path";
|
|
16977
17995
|
init_config();
|
|
16978
17996
|
init_data();
|
|
16979
17997
|
var PROOF_REVIEW_FILE = "proof-reviews.jsonl";
|
|
16980
17998
|
var MEASURING_WINDOW_DAYS = 2;
|
|
16981
17999
|
function proofPath() {
|
|
16982
|
-
return
|
|
18000
|
+
return join23(getHelixDir(), PROOF_REVIEW_FILE);
|
|
16983
18001
|
}
|
|
16984
18002
|
function uniqueStrings3(values) {
|
|
16985
18003
|
return [...new Set(values.filter((value) => Boolean(value && value.trim())))];
|
|
@@ -16998,9 +18016,9 @@ function buildRecordId(targetType, targetId) {
|
|
|
16998
18016
|
}
|
|
16999
18017
|
function readProofReviews() {
|
|
17000
18018
|
const path = proofPath();
|
|
17001
|
-
if (!
|
|
18019
|
+
if (!existsSync18(path))
|
|
17002
18020
|
return [];
|
|
17003
|
-
const raw =
|
|
18021
|
+
const raw = readFileSync15(path, "utf-8").trim();
|
|
17004
18022
|
if (!raw)
|
|
17005
18023
|
return [];
|
|
17006
18024
|
return raw.split(`
|
|
@@ -17476,7 +18494,7 @@ Examples:
|
|
|
17476
18494
|
$ helixevo graph --mermaid Open graph in browser
|
|
17477
18495
|
$ helixevo graph --obsidian ~/vault Sync to Obsidian vault
|
|
17478
18496
|
$ helixevo graph --rebuild Re-infer relationships (LLM call)
|
|
17479
|
-
$ helixevo graph --optimize
|
|
18497
|
+
$ helixevo graph --optimize Refresh topology review queue first, then report full vs partial enrichment
|
|
17480
18498
|
$ helixevo topology --status Show reviewed topology execution state
|
|
17481
18499
|
$ helixevo topology --prepare <id> Prepare an accepted topology candidate
|
|
17482
18500
|
$ helixevo topology --apply <planId> Apply a safe prepared topology plan
|
|
@@ -17495,12 +18513,12 @@ program2.command("capture").description("Capture failures from a Craft Agent ses
|
|
|
17495
18513
|
program2.command("evolve").description("Evolve skills from failures [--dry-run] [--verbose] [--max-proposals <n>]").option("--dry-run", "Show proposals without applying").option("--verbose", "Show detailed LLM interactions").option("--max-proposals <n>", "Max proposals per run", "5").action(evolveCommand);
|
|
17496
18514
|
program2.command("generalize").description("Promote cross-skill patterns to higher layer ↑ [--dry-run] [--verbose]").option("--dry-run", "Show candidates without applying").option("--verbose", "Show detailed analysis").action(generalizeCommand);
|
|
17497
18515
|
program2.command("specialize").description("Create project-specific skills ↓ --project <name> [--dry-run] [--verbose]").requiredOption("--project <name>", "Project name").option("--dry-run", "Show candidates without applying").option("--verbose", "Show detailed analysis").action(specializeCommand);
|
|
17498
|
-
program2.command("graph").description("Skill network [--mermaid] [--obsidian <path>] [--rebuild] [--optimize refreshes topology review]").option("--mermaid", "Render as Mermaid diagram in browser").option("--rebuild", "Force rebuild (re-infer relationships via LLM)").option("--obsidian <path>", "Sync to Obsidian vault at path").option("--optimize", "Run network optimization (merge/split/conflict detection)").option("--verbose", "Show detailed analysis").action(graphCommand);
|
|
18516
|
+
program2.command("graph").description("Skill network [--mermaid] [--obsidian <path>] [--rebuild] [--optimize refreshes topology review and reports full vs partial enrichment]").option("--mermaid", "Render as Mermaid diagram in browser").option("--rebuild", "Force rebuild (re-infer relationships via LLM)").option("--obsidian <path>", "Sync to Obsidian vault at path").option("--optimize", "Run network optimization (merge/split/conflict detection)").option("--verbose", "Show detailed analysis").action(graphCommand);
|
|
17499
18517
|
program2.command("research").description("Proactive research via web [--project <path>] [--dry-run] [--verbose]").option("--project <path>", "Project path for goal extraction").option("--dry-run", "Show discoveries without creating skills").option("--verbose", "Show detailed research steps").option("--max-hypotheses <n>", "Max hypotheses to test", "3").action(researchCommand);
|
|
17500
18518
|
program2.command("dashboard").description("Open web dashboard (default: http://localhost:3847, falls forward if occupied)").option("--background", "Run in background (detach from terminal)").option("--stop", "Stop a background dashboard").option("--port <port>", "Preferred port to try first (default: 3847)").option("--no-auto-update", "Skip automatic update check before launching the dashboard").action(async (options) => {
|
|
17501
18519
|
await dashboardCommand(options);
|
|
17502
18520
|
});
|
|
17503
|
-
program2.command("status").description("Show frontier, skills, failures, and
|
|
18521
|
+
program2.command("status").description("Show frontier, skills, failures, network health, and provider control truth").action(statusCommand);
|
|
17504
18522
|
program2.command("report").description("Evolution report [--days <n>] [--output <path>]").option("--days <n>", "Report period in days", "1").option("--output <path>", "Output path for report").action(reportCommand);
|
|
17505
18523
|
program2.command("watch").description("Always-on learning: auto-capture corrections + auto-evolve").option("--project <name>", "Project name for captured failures").option("--events <path>", "Path to events.jsonl (default: ./events.jsonl)").option("--verbose", "Show detailed capture and metrics").option("--no-evolve", "Disable auto-evolution (capture only)").action(watchCommand);
|
|
17506
18524
|
program2.command("health").description("Assess network health: cohesion, coverage, balance, cross-project transfer").option("--verbose", "Show detailed analysis").action(async (options) => {
|
|
@@ -17516,4 +18534,10 @@ program2.command("ontology").description("Governed ontology frontier and semanti
|
|
|
17516
18534
|
program2.hook("postAction", () => {
|
|
17517
18535
|
checkForUpdates().catch(() => {});
|
|
17518
18536
|
});
|
|
17519
|
-
|
|
18537
|
+
try {
|
|
18538
|
+
await program2.parseAsync(process.argv);
|
|
18539
|
+
} catch (error) {
|
|
18540
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
18541
|
+
console.error(message);
|
|
18542
|
+
process.exit(1);
|
|
18543
|
+
}
|