runtrim 0.1.6 → 0.1.8
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/dist-cli/runtrim.js +854 -179
- package/package.json +3 -1
package/dist-cli/runtrim.js
CHANGED
|
@@ -25,8 +25,9 @@ import { Chalk } from "chalk";
|
|
|
25
25
|
import ora from "ora";
|
|
26
26
|
import prompts from "prompts";
|
|
27
27
|
import clipboard from "clipboardy";
|
|
28
|
-
import
|
|
29
|
-
import
|
|
28
|
+
import fs10 from "fs";
|
|
29
|
+
import path10 from "path";
|
|
30
|
+
import os3 from "os";
|
|
30
31
|
import { execa as execa3 } from "execa";
|
|
31
32
|
|
|
32
33
|
// src/lib/runtrim-config.ts
|
|
@@ -1731,6 +1732,16 @@ var SyncRunSchema = z2.object({
|
|
|
1731
1732
|
localId: z2.string(),
|
|
1732
1733
|
task: z2.string(),
|
|
1733
1734
|
status: z2.string(),
|
|
1735
|
+
// Bridge Mode optional fields
|
|
1736
|
+
goal: z2.string().optional(),
|
|
1737
|
+
allowedScope: z2.array(z2.string()).optional(),
|
|
1738
|
+
forbiddenScope: z2.array(z2.string()).optional(),
|
|
1739
|
+
stopConditions: z2.array(z2.string()).optional(),
|
|
1740
|
+
memoryUsed: z2.boolean().optional(),
|
|
1741
|
+
memorySummary: z2.string().optional(),
|
|
1742
|
+
tokenBudget: z2.number().optional(),
|
|
1743
|
+
scopeDriftStatus: z2.string().optional(),
|
|
1744
|
+
reportSummary: z2.string().optional(),
|
|
1734
1745
|
createdAt: z2.string(),
|
|
1735
1746
|
evaluatedAt: z2.string().nullable(),
|
|
1736
1747
|
riskBefore: z2.string().nullable(),
|
|
@@ -1847,7 +1858,7 @@ function buildSyncPayload(input) {
|
|
|
1847
1858
|
);
|
|
1848
1859
|
const memoryNextSafePrompt = readSection(memoryMarkdown, "Next safe prompt");
|
|
1849
1860
|
const mappedRuns = runs.slice(0, 200).map((run) => {
|
|
1850
|
-
var _a3, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k2, _l2, _m2, _n2, _o2, _p2, _q2, _r2, _s2, _t2, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I;
|
|
1861
|
+
var _a3, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k2, _l2, _m2, _n2, _o2, _p2, _q2, _r2, _s2, _t2, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __;
|
|
1851
1862
|
const tokens = Math.round(parseEstimatedNumber(String(run.contract.estimatedTokensTrimmed)));
|
|
1852
1863
|
const dollars = estimateSavingsFromTokens(tokens);
|
|
1853
1864
|
const status = (_b2 = (_a3 = run.evaluation) == null ? void 0 : _a3.status) != null ? _b2 : run.status;
|
|
@@ -1882,7 +1893,17 @@ function buildSyncPayload(input) {
|
|
|
1882
1893
|
nextSafePrompt: (_I = (_H = (_G = (_D = (_B = (_z = run.evaluation) == null ? void 0 : _z.nextPrompt) != null ? _B : (_A = run.evaluation) == null ? void 0 : _A.nextSafePrompt) != null ? _D : (_C = run.evaluation) == null ? void 0 : _C.nextGuardedPrompt) != null ? _G : (_F = (_E = run.contract) == null ? void 0 : _E.splitReport) == null ? void 0 : _F.nextSafePrompt) != null ? _H : fallbackNextPrompt) != null ? _I : null,
|
|
1883
1894
|
latestPrompt: latestPromptText,
|
|
1884
1895
|
continuationPrompt: continuationPromptText,
|
|
1885
|
-
fallbackNextPrompt
|
|
1896
|
+
fallbackNextPrompt,
|
|
1897
|
+
// Bridge Mode fields
|
|
1898
|
+
goal: ((_K = (_J = run.contract) == null ? void 0 : _J.contract) == null ? void 0 : _K.cleanedObjective) || void 0,
|
|
1899
|
+
allowedScope: ((_N = (_M = (_L = run.contract) == null ? void 0 : _L.contract) == null ? void 0 : _M.relevantScope) == null ? void 0 : _N.length) ? run.contract.contract.relevantScope : void 0,
|
|
1900
|
+
forbiddenScope: ((_Q = (_P = (_O = run.contract) == null ? void 0 : _O.contract) == null ? void 0 : _P.forbiddenScope) == null ? void 0 : _Q.length) ? run.contract.contract.forbiddenScope : void 0,
|
|
1901
|
+
stopConditions: ((_T = (_S = (_R = run.contract) == null ? void 0 : _R.contract) == null ? void 0 : _S.stopRules) == null ? void 0 : _T.length) ? run.contract.contract.stopRules : void 0,
|
|
1902
|
+
memoryUsed: (_U = run.memoryUsed) != null ? _U : void 0,
|
|
1903
|
+
memorySummary: (_X = (_W = run.memorySummary) != null ? _W : (_V = run.evaluation) == null ? void 0 : _V.memorySummary) != null ? _X : void 0,
|
|
1904
|
+
tokenBudget: (_Y = run.tokenBudget) != null ? _Y : void 0,
|
|
1905
|
+
scopeDriftStatus: (_Z = run.scopeDriftStatus) != null ? _Z : void 0,
|
|
1906
|
+
reportSummary: (__ = run.reportSummary) != null ? __ : void 0
|
|
1886
1907
|
};
|
|
1887
1908
|
});
|
|
1888
1909
|
const payload = {
|
|
@@ -1921,6 +1942,253 @@ function buildSyncPayload(input) {
|
|
|
1921
1942
|
return SyncPayloadSchema.parse(payload);
|
|
1922
1943
|
}
|
|
1923
1944
|
|
|
1945
|
+
// src/lib/bridge.ts
|
|
1946
|
+
import fs7 from "fs";
|
|
1947
|
+
import path7 from "path";
|
|
1948
|
+
var BRIDGE_START = "<!-- RUNTRIM_BRIDGE_START -->";
|
|
1949
|
+
var BRIDGE_END = "<!-- RUNTRIM_BRIDGE_END -->";
|
|
1950
|
+
var BRIDGE_BLOCK = `
|
|
1951
|
+
${BRIDGE_START}
|
|
1952
|
+
Before editing code, read RUNTRIM.md and .runtrim/contracts/latest.md.
|
|
1953
|
+
Stay inside the active scoped contract.
|
|
1954
|
+
Do not touch forbidden files or unrelated systems.
|
|
1955
|
+
If scope expands, stop and ask for a new RunTrim run.
|
|
1956
|
+
After editing, run: runtrim finish
|
|
1957
|
+
${BRIDGE_END}
|
|
1958
|
+
`;
|
|
1959
|
+
var TOKEN_BUDGET_MAP = {
|
|
1960
|
+
low: 1e4,
|
|
1961
|
+
medium: 25e3,
|
|
1962
|
+
high: 4e4,
|
|
1963
|
+
critical: 5e4
|
|
1964
|
+
};
|
|
1965
|
+
function deriveBridgeContext(task, contract, recentRuns, projectName) {
|
|
1966
|
+
var _a2, _b, _c, _d, _e, _f, _g;
|
|
1967
|
+
const c = contract.contract;
|
|
1968
|
+
const riskLevel = (_a2 = contract.wasteRiskAfter) != null ? _a2 : "medium";
|
|
1969
|
+
const tokenBudget = (_b = TOKEN_BUDGET_MAP[riskLevel]) != null ? _b : 25e3;
|
|
1970
|
+
const stopConditions = [
|
|
1971
|
+
...(_c = c.stopRules) != null ? _c : [],
|
|
1972
|
+
...((_d = c.whenToAsk) != null ? _d : []).map((s) => `Stop if: ${s}`)
|
|
1973
|
+
];
|
|
1974
|
+
const verificationSteps = (_e = c.successCriteria) != null ? _e : [];
|
|
1975
|
+
const lines = recentRuns.slice(0, 3).map((r) => {
|
|
1976
|
+
const day = new Date(r.createdAt).toLocaleDateString("en-US", {
|
|
1977
|
+
month: "short",
|
|
1978
|
+
day: "numeric"
|
|
1979
|
+
});
|
|
1980
|
+
return `- ${r.task} (${r.status}, ${day})`;
|
|
1981
|
+
});
|
|
1982
|
+
const memoryContext = lines.length > 0 ? `Recent runs:
|
|
1983
|
+
${lines.join("\n")}` : "No prior runs. This is the first run for this project.";
|
|
1984
|
+
return {
|
|
1985
|
+
task,
|
|
1986
|
+
goal: c.cleanedObjective || task,
|
|
1987
|
+
riskLevel,
|
|
1988
|
+
tokenBudget,
|
|
1989
|
+
allowedScope: (_f = c.relevantScope) != null ? _f : [],
|
|
1990
|
+
forbiddenScope: (_g = c.forbiddenScope) != null ? _g : [],
|
|
1991
|
+
stopConditions,
|
|
1992
|
+
verificationSteps,
|
|
1993
|
+
memoryContext,
|
|
1994
|
+
projectName
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
function writeContractFile(ctx, cwd = process.cwd()) {
|
|
1998
|
+
const dir = path7.join(getConfigDir(cwd), "contracts");
|
|
1999
|
+
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
2000
|
+
const scopeLines = ctx.allowedScope.length > 0 ? ctx.allowedScope.map((s) => `- ${s}`) : ["- Scope not explicitly defined. Stay close to the task description."];
|
|
2001
|
+
const forbidLines = ctx.forbiddenScope.length > 0 ? ctx.forbiddenScope.map((s) => `- ${s}`) : ["- .env* files", "- auth logic", "- database schema / migrations", "- billing and payment logic"];
|
|
2002
|
+
const stopLines = ctx.stopConditions.length > 0 ? ctx.stopConditions.map((s) => `- ${s}`) : [
|
|
2003
|
+
"- Stop if scope must expand beyond the task.",
|
|
2004
|
+
"- Stop if a forbidden area needs to be touched.",
|
|
2005
|
+
"- Stop if more than 5 files require changes."
|
|
2006
|
+
];
|
|
2007
|
+
const verifyLines = ctx.verificationSteps.length > 0 ? ctx.verificationSteps.map((s) => `- ${s}`) : ["- Verify the behavior described in the task.", "- Check no unrelated files changed."];
|
|
2008
|
+
const lines = [
|
|
2009
|
+
"# RunTrim Active Contract",
|
|
2010
|
+
"",
|
|
2011
|
+
`Task: ${ctx.task}`,
|
|
2012
|
+
`Goal: ${ctx.goal}`,
|
|
2013
|
+
`Risk: ${ctx.riskLevel}`,
|
|
2014
|
+
`Token budget: ~${ctx.tokenBudget.toLocaleString()}`,
|
|
2015
|
+
`Created: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
2016
|
+
"",
|
|
2017
|
+
"## Allowed scope",
|
|
2018
|
+
...scopeLines,
|
|
2019
|
+
"",
|
|
2020
|
+
"## Forbidden scope",
|
|
2021
|
+
...forbidLines,
|
|
2022
|
+
"",
|
|
2023
|
+
"## Stop conditions",
|
|
2024
|
+
...stopLines,
|
|
2025
|
+
"",
|
|
2026
|
+
"## Verification steps",
|
|
2027
|
+
...verifyLines,
|
|
2028
|
+
"",
|
|
2029
|
+
"---",
|
|
2030
|
+
"Generated by RunTrim Bridge. Do not edit manually."
|
|
2031
|
+
];
|
|
2032
|
+
fs7.writeFileSync(path7.join(dir, "latest.md"), lines.join("\n"), "utf-8");
|
|
2033
|
+
}
|
|
2034
|
+
function writeMemoryFile(ctx, cwd = process.cwd()) {
|
|
2035
|
+
const dir = path7.join(getConfigDir(cwd), "memory");
|
|
2036
|
+
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
2037
|
+
const noTouchLines = ctx.forbiddenScope.length > 0 ? ctx.forbiddenScope.map((s) => `- ${s}`) : ["- .env files", "- auth logic", "- database schema", "- billing and payment logic"];
|
|
2038
|
+
const scopeLines = ctx.allowedScope.length > 0 ? ctx.allowedScope.map((s) => `- ${s}`) : ["- Defined in .runtrim/contracts/latest.md"];
|
|
2039
|
+
const lines = [
|
|
2040
|
+
"# RunTrim Memory Pack",
|
|
2041
|
+
"",
|
|
2042
|
+
`Project: ${ctx.projectName}`,
|
|
2043
|
+
`Current task: ${ctx.task}`,
|
|
2044
|
+
`Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
2045
|
+
"",
|
|
2046
|
+
"## Context",
|
|
2047
|
+
ctx.memoryContext,
|
|
2048
|
+
"",
|
|
2049
|
+
"## Do not touch",
|
|
2050
|
+
...noTouchLines,
|
|
2051
|
+
"",
|
|
2052
|
+
"## Active task scope",
|
|
2053
|
+
...scopeLines,
|
|
2054
|
+
"",
|
|
2055
|
+
"---",
|
|
2056
|
+
"Generated by RunTrim Bridge. Do not edit manually."
|
|
2057
|
+
];
|
|
2058
|
+
fs7.writeFileSync(path7.join(dir, "current.md"), lines.join("\n"), "utf-8");
|
|
2059
|
+
}
|
|
2060
|
+
function writeBridgeInstructions(cwd = process.cwd()) {
|
|
2061
|
+
const dir = path7.join(getConfigDir(cwd), "bridge");
|
|
2062
|
+
if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
|
|
2063
|
+
const lines = [
|
|
2064
|
+
"# RunTrim Agent Instructions",
|
|
2065
|
+
"",
|
|
2066
|
+
"You are operating inside a RunTrim-guarded session.",
|
|
2067
|
+
"",
|
|
2068
|
+
"## Before editing",
|
|
2069
|
+
"",
|
|
2070
|
+
"Read these files first:",
|
|
2071
|
+
"- RUNTRIM.md",
|
|
2072
|
+
"- .runtrim/contracts/latest.md",
|
|
2073
|
+
"- .runtrim/memory/current.md",
|
|
2074
|
+
"",
|
|
2075
|
+
"## During editing",
|
|
2076
|
+
"",
|
|
2077
|
+
"- Stay within the allowed scope defined in the contract.",
|
|
2078
|
+
"- If a change requires touching a forbidden area, STOP and tell the user.",
|
|
2079
|
+
"- Make minimal, targeted changes only.",
|
|
2080
|
+
"- Do not refactor, rename, or reorganize outside the task scope.",
|
|
2081
|
+
"- Do not add console.log statements or debug artifacts.",
|
|
2082
|
+
"- Do not install new dependencies without explicit approval.",
|
|
2083
|
+
"",
|
|
2084
|
+
"## After editing",
|
|
2085
|
+
"",
|
|
2086
|
+
"- Tell the user which files you changed and why.",
|
|
2087
|
+
"- The user will run: runtrim finish",
|
|
2088
|
+
"- Do not run runtrim commands yourself unless explicitly asked.",
|
|
2089
|
+
"",
|
|
2090
|
+
"---",
|
|
2091
|
+
"Generated by RunTrim Bridge. Do not edit manually."
|
|
2092
|
+
];
|
|
2093
|
+
fs7.writeFileSync(path7.join(dir, "agent-instructions.md"), lines.join("\n"), "utf-8");
|
|
2094
|
+
}
|
|
2095
|
+
function writeRootProtocolFile(ctx, cwd = process.cwd()) {
|
|
2096
|
+
const lines = [
|
|
2097
|
+
"# RunTrim Protocol",
|
|
2098
|
+
"",
|
|
2099
|
+
"This project is guarded by RunTrim Bridge Mode.",
|
|
2100
|
+
"",
|
|
2101
|
+
"## Active session",
|
|
2102
|
+
"",
|
|
2103
|
+
`Task: ${ctx.task}`,
|
|
2104
|
+
`Risk: ${ctx.riskLevel}`,
|
|
2105
|
+
`Token budget: ~${ctx.tokenBudget.toLocaleString()}`,
|
|
2106
|
+
"",
|
|
2107
|
+
"## Before editing",
|
|
2108
|
+
"",
|
|
2109
|
+
"Read the active contract before making any changes:",
|
|
2110
|
+
"",
|
|
2111
|
+
"```",
|
|
2112
|
+
".runtrim/contracts/latest.md",
|
|
2113
|
+
"```",
|
|
2114
|
+
"",
|
|
2115
|
+
"## Rules",
|
|
2116
|
+
"",
|
|
2117
|
+
"- Stay inside the active scoped contract.",
|
|
2118
|
+
"- Do not touch forbidden files or unrelated systems.",
|
|
2119
|
+
"- If scope must expand, stop and ask the user to start a new RunTrim session.",
|
|
2120
|
+
"- Keep changes minimal and targeted.",
|
|
2121
|
+
"- Do not refactor, reorganize, or rename outside the direct task.",
|
|
2122
|
+
"",
|
|
2123
|
+
"## After editing",
|
|
2124
|
+
"",
|
|
2125
|
+
"The user will run: runtrim finish",
|
|
2126
|
+
"",
|
|
2127
|
+
"Do not attempt to run runtrim commands yourself.",
|
|
2128
|
+
"",
|
|
2129
|
+
"---",
|
|
2130
|
+
`Generated by RunTrim Bridge. Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
2131
|
+
];
|
|
2132
|
+
fs7.writeFileSync(path7.join(cwd, "RUNTRIM.md"), lines.join("\n"), "utf-8");
|
|
2133
|
+
}
|
|
2134
|
+
function appendBridgeBlock(filePath) {
|
|
2135
|
+
if (!fs7.existsSync(filePath)) return false;
|
|
2136
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
2137
|
+
if (content.includes(BRIDGE_START)) return false;
|
|
2138
|
+
fs7.writeFileSync(filePath, content.trimEnd() + "\n" + BRIDGE_BLOCK, "utf-8");
|
|
2139
|
+
return true;
|
|
2140
|
+
}
|
|
2141
|
+
function writeBridgeFiles(ctx, cwd) {
|
|
2142
|
+
const written = [];
|
|
2143
|
+
const managedPaths = [];
|
|
2144
|
+
const track = (relativePath, label) => {
|
|
2145
|
+
managedPaths.push(relativePath);
|
|
2146
|
+
written.push(label != null ? label : relativePath);
|
|
2147
|
+
};
|
|
2148
|
+
writeRootProtocolFile(ctx, cwd);
|
|
2149
|
+
track("RUNTRIM.md");
|
|
2150
|
+
writeContractFile(ctx, cwd);
|
|
2151
|
+
track(".runtrim/contracts/latest.md");
|
|
2152
|
+
writeMemoryFile(ctx, cwd);
|
|
2153
|
+
track(".runtrim/memory/current.md");
|
|
2154
|
+
writeBridgeInstructions(cwd);
|
|
2155
|
+
track(".runtrim/bridge/agent-instructions.md");
|
|
2156
|
+
if (appendBridgeBlock(path7.join(cwd, "CLAUDE.md"))) track("CLAUDE.md", "CLAUDE.md (bridge block appended)");
|
|
2157
|
+
if (appendBridgeBlock(path7.join(cwd, "AGENTS.md"))) track("AGENTS.md", "AGENTS.md (bridge block appended)");
|
|
2158
|
+
return { written, managedPaths };
|
|
2159
|
+
}
|
|
2160
|
+
function buildBridgePrompt(contractText, ctx) {
|
|
2161
|
+
const allowedList = ctx.allowedScope.length > 0 ? ctx.allowedScope.map((s) => ` - ${s}`).join("\n") : " - Defined in .runtrim/contracts/latest.md";
|
|
2162
|
+
const forbidList = ctx.forbiddenScope.length > 0 ? ctx.forbiddenScope.map((s) => ` - ${s}`).join("\n") : " - .env files, auth, database schema, billing, payments";
|
|
2163
|
+
const stopList = ctx.stopConditions.length > 0 ? ctx.stopConditions.slice(0, 4).map((s) => ` - ${s}`).join("\n") : " - Stop if more than 5 files need changes.\n - Stop if a forbidden area must be touched.";
|
|
2164
|
+
const header = [
|
|
2165
|
+
"RUNTRIM BRIDGE SESSION",
|
|
2166
|
+
`Task: ${ctx.task}`,
|
|
2167
|
+
`Risk: ${ctx.riskLevel} | Token budget: ~${ctx.tokenBudget.toLocaleString()}`,
|
|
2168
|
+
"",
|
|
2169
|
+
"ALLOWED SCOPE",
|
|
2170
|
+
allowedList,
|
|
2171
|
+
"",
|
|
2172
|
+
"FORBIDDEN SCOPE",
|
|
2173
|
+
forbidList,
|
|
2174
|
+
"",
|
|
2175
|
+
"STOP CONDITIONS",
|
|
2176
|
+
stopList,
|
|
2177
|
+
""
|
|
2178
|
+
].join("\n");
|
|
2179
|
+
const footer = [
|
|
2180
|
+
"",
|
|
2181
|
+
"RULES",
|
|
2182
|
+
" - Read RUNTRIM.md and .runtrim/contracts/latest.md before touching any file.",
|
|
2183
|
+
" - Inspect first. List relevant files before opening them.",
|
|
2184
|
+
" - Make the minimal change required. No unrelated refactors.",
|
|
2185
|
+
" - If scope must expand, stop immediately and tell the user.",
|
|
2186
|
+
" - After editing, tell the user which files changed. They will run: runtrim finish",
|
|
2187
|
+
""
|
|
2188
|
+
].join("\n");
|
|
2189
|
+
return header + contractText + footer;
|
|
2190
|
+
}
|
|
2191
|
+
|
|
1924
2192
|
// src/lib/run-watch.ts
|
|
1925
2193
|
function normalizeScopeKeywords2(scope) {
|
|
1926
2194
|
var _a2;
|
|
@@ -2017,18 +2285,18 @@ function evaluateWatchState(input) {
|
|
|
2017
2285
|
}
|
|
2018
2286
|
|
|
2019
2287
|
// src/lib/local-panel-server.ts
|
|
2020
|
-
import
|
|
2288
|
+
import fs9 from "fs";
|
|
2021
2289
|
import http from "http";
|
|
2022
2290
|
import net from "net";
|
|
2023
2291
|
import os2 from "os";
|
|
2024
|
-
import
|
|
2292
|
+
import path9 from "path";
|
|
2025
2293
|
import { spawn } from "child_process";
|
|
2026
2294
|
|
|
2027
2295
|
// src/lib/global-registry.ts
|
|
2028
2296
|
import crypto from "crypto";
|
|
2029
|
-
import
|
|
2297
|
+
import fs8 from "fs";
|
|
2030
2298
|
import os from "os";
|
|
2031
|
-
import
|
|
2299
|
+
import path8 from "path";
|
|
2032
2300
|
import { execa as execa2 } from "execa";
|
|
2033
2301
|
var DEFAULT_REGISTRY = {
|
|
2034
2302
|
version: 1,
|
|
@@ -2040,23 +2308,23 @@ var DEFAULT_REGISTRY = {
|
|
|
2040
2308
|
}
|
|
2041
2309
|
};
|
|
2042
2310
|
function normalizeRepoPath(input) {
|
|
2043
|
-
const resolved =
|
|
2311
|
+
const resolved = path8.resolve(input);
|
|
2044
2312
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
2045
2313
|
}
|
|
2046
2314
|
function hashValue(value) {
|
|
2047
2315
|
return crypto.createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
2048
2316
|
}
|
|
2049
2317
|
function getGlobalRunTrimDir() {
|
|
2050
|
-
return
|
|
2318
|
+
return path8.join(os.homedir(), ".runtrim");
|
|
2051
2319
|
}
|
|
2052
2320
|
function getGlobalRegistryPath() {
|
|
2053
|
-
return
|
|
2321
|
+
return path8.join(getGlobalRunTrimDir(), "global.json");
|
|
2054
2322
|
}
|
|
2055
2323
|
function loadGlobalRegistry() {
|
|
2056
2324
|
const registryPath = getGlobalRegistryPath();
|
|
2057
|
-
if (!
|
|
2325
|
+
if (!fs8.existsSync(registryPath)) return __spreadValues({}, DEFAULT_REGISTRY);
|
|
2058
2326
|
try {
|
|
2059
|
-
const raw = JSON.parse(
|
|
2327
|
+
const raw = JSON.parse(fs8.readFileSync(registryPath, "utf-8"));
|
|
2060
2328
|
return {
|
|
2061
2329
|
version: 1,
|
|
2062
2330
|
plan: raw.plan === "free" ? "free" : "free",
|
|
@@ -2079,8 +2347,8 @@ function loadGlobalRegistry() {
|
|
|
2079
2347
|
}
|
|
2080
2348
|
function saveGlobalRegistry(registry) {
|
|
2081
2349
|
const dir = getGlobalRunTrimDir();
|
|
2082
|
-
if (!
|
|
2083
|
-
|
|
2350
|
+
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
2351
|
+
fs8.writeFileSync(getGlobalRegistryPath(), JSON.stringify(registry, null, 2), "utf-8");
|
|
2084
2352
|
}
|
|
2085
2353
|
async function getCurrentRepoIdentity(cwd = process.cwd()) {
|
|
2086
2354
|
const normalizedPath = normalizeRepoPath(cwd);
|
|
@@ -2098,7 +2366,7 @@ async function getCurrentRepoIdentity(cwd = process.cwd()) {
|
|
|
2098
2366
|
const idPrefix = gitRemote ? "remote" : "path";
|
|
2099
2367
|
return {
|
|
2100
2368
|
id: `${idPrefix}_${hashValue(idSeed)}`,
|
|
2101
|
-
name:
|
|
2369
|
+
name: path8.basename(normalizedPath),
|
|
2102
2370
|
path: normalizedPath,
|
|
2103
2371
|
gitRemote
|
|
2104
2372
|
};
|
|
@@ -2189,9 +2457,9 @@ function truncate2(text, max = 96) {
|
|
|
2189
2457
|
return `${text.slice(0, max - 3)}...`;
|
|
2190
2458
|
}
|
|
2191
2459
|
function readMemory2(cwd) {
|
|
2192
|
-
const p =
|
|
2193
|
-
if (!
|
|
2194
|
-
return
|
|
2460
|
+
const p = path9.join(getConfigDir(cwd), "memory.md");
|
|
2461
|
+
if (!fs9.existsSync(p)) return null;
|
|
2462
|
+
return fs9.readFileSync(p, "utf-8");
|
|
2195
2463
|
}
|
|
2196
2464
|
function parseMemorySection(memory, title) {
|
|
2197
2465
|
if (!memory) return "";
|
|
@@ -2215,13 +2483,13 @@ function readProjectName(cwd) {
|
|
|
2215
2483
|
audit = null;
|
|
2216
2484
|
}
|
|
2217
2485
|
if (audit == null ? void 0 : audit.projectName) return audit.projectName;
|
|
2218
|
-
const pkgPath =
|
|
2219
|
-
if (!
|
|
2486
|
+
const pkgPath = path9.join(cwd, "package.json");
|
|
2487
|
+
if (!fs9.existsSync(pkgPath)) return path9.basename(cwd);
|
|
2220
2488
|
try {
|
|
2221
|
-
const pkg = JSON.parse(
|
|
2222
|
-
return ((_a2 = pkg.name) == null ? void 0 : _a2.trim()) ||
|
|
2489
|
+
const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
|
|
2490
|
+
return ((_a2 = pkg.name) == null ? void 0 : _a2.trim()) || path9.basename(cwd);
|
|
2223
2491
|
} catch (e) {
|
|
2224
|
-
return
|
|
2492
|
+
return path9.basename(cwd);
|
|
2225
2493
|
}
|
|
2226
2494
|
}
|
|
2227
2495
|
function isoOrEmpty(value) {
|
|
@@ -2912,7 +3180,7 @@ async function getPanelState(cwd, monitorMode) {
|
|
|
2912
3180
|
warnings,
|
|
2913
3181
|
initialized,
|
|
2914
3182
|
projectName: readProjectName(cwd),
|
|
2915
|
-
repoPath:
|
|
3183
|
+
repoPath: path9.resolve(cwd),
|
|
2916
3184
|
mode: "local",
|
|
2917
3185
|
monitor: monitorMode,
|
|
2918
3186
|
stack: (_a2 = projectAudit == null ? void 0 : projectAudit.detectedStack) != null ? _a2 : [],
|
|
@@ -2930,7 +3198,7 @@ async function getPanelState(cwd, monitorMode) {
|
|
|
2930
3198
|
changedFilesCount: (_k = (_j = (_h = (_g = latest.evaluation) == null ? void 0 : _g.changedFiles) == null ? void 0 : _h.length) != null ? _j : (_i = latest.watchChangedFiles) == null ? void 0 : _i.length) != null ? _k : 0,
|
|
2931
3199
|
riskLabel: ((_o = (_n = (_l = latest.evaluation) == null ? void 0 : _l.scopeDriftRisk) != null ? _n : (_m = latest.audit) == null ? void 0 : _m.wasteRiskBefore) != null ? _o : "n/a").replace(/_/g, " "),
|
|
2932
3200
|
nextSafeAction: (_q = (_p = latest.evaluation) == null ? void 0 : _p.nextSafeAction) != null ? _q : "Run runtrim check to evaluate latest run.",
|
|
2933
|
-
promptState: ((_r = latest.contract) == null ? void 0 : _r.isBlocked) ? "split required prompt" :
|
|
3201
|
+
promptState: ((_r = latest.contract) == null ? void 0 : _r.isBlocked) ? "split required prompt" : fs9.existsSync(path9.join(getConfigDir(cwd), "latest-prompt.md")) ? "guarded prompt available" : "no continuation prompt yet",
|
|
2934
3202
|
missingProofCount: (_u = (_t = (_s = latest.evaluation) == null ? void 0 : _s.missingProofItems) == null ? void 0 : _t.length) != null ? _u : 0,
|
|
2935
3203
|
missingProofLabel: ((_w = (_v = latest.evaluation) == null ? void 0 : _v.missingProofItems) == null ? void 0 : _w.length) ? `${latest.evaluation.missingProofItems.length} verification item(s) still missing` : "No missing proof recorded",
|
|
2936
3204
|
proofState: ((_y = (_x = latest.evaluation) == null ? void 0 : _x.missingProofItems) == null ? void 0 : _y.length) && latest.evaluation.missingProofItems.length > 0 ? "Proof pending" : latest.status === "guarded" ? "Needs check" : "Proof captured",
|
|
@@ -3038,8 +3306,8 @@ async function startLocalPanelServer(options = {}) {
|
|
|
3038
3306
|
error: "partial_state",
|
|
3039
3307
|
warnings: ["state_route_failed"],
|
|
3040
3308
|
initialized: false,
|
|
3041
|
-
projectName:
|
|
3042
|
-
repoPath:
|
|
3309
|
+
projectName: path9.basename(cwd),
|
|
3310
|
+
repoPath: path9.resolve(cwd),
|
|
3043
3311
|
mode: "local",
|
|
3044
3312
|
monitor,
|
|
3045
3313
|
stack: [],
|
|
@@ -3221,29 +3489,29 @@ function resolveCliLauncherPath() {
|
|
|
3221
3489
|
var _a2;
|
|
3222
3490
|
const invokedPath = (_a2 = process.argv[1]) == null ? void 0 : _a2.trim();
|
|
3223
3491
|
if (invokedPath) {
|
|
3224
|
-
const absolute =
|
|
3225
|
-
if (
|
|
3492
|
+
const absolute = path10.resolve(invokedPath);
|
|
3493
|
+
if (fs10.existsSync(absolute)) return absolute;
|
|
3226
3494
|
}
|
|
3227
|
-
const localFallback =
|
|
3228
|
-
if (
|
|
3495
|
+
const localFallback = path10.resolve(process.cwd(), "dist-cli", "runtrim.cjs");
|
|
3496
|
+
if (fs10.existsSync(localFallback)) return localFallback;
|
|
3229
3497
|
return null;
|
|
3230
3498
|
}
|
|
3231
3499
|
function resolveCliRuntimeDir() {
|
|
3232
3500
|
const launcher = resolveCliLauncherPath();
|
|
3233
|
-
if (launcher) return
|
|
3501
|
+
if (launcher) return path10.dirname(launcher);
|
|
3234
3502
|
return process.cwd();
|
|
3235
3503
|
}
|
|
3236
3504
|
function resolveCliVersion() {
|
|
3237
3505
|
var _a2, _b;
|
|
3238
3506
|
const cliDir = resolveCliRuntimeDir();
|
|
3239
3507
|
const candidates = [
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3508
|
+
path10.resolve(cliDir, "..", "package.json"),
|
|
3509
|
+
path10.resolve(cliDir, "package.json"),
|
|
3510
|
+
path10.resolve(process.cwd(), "package.json")
|
|
3243
3511
|
];
|
|
3244
3512
|
for (const packageJsonPath of candidates) {
|
|
3245
3513
|
try {
|
|
3246
|
-
const raw =
|
|
3514
|
+
const raw = fs10.readFileSync(packageJsonPath, "utf-8");
|
|
3247
3515
|
const parsed = JSON.parse(raw);
|
|
3248
3516
|
if (parsed.version && parsed.version.trim()) return parsed.version.trim();
|
|
3249
3517
|
} catch (e) {
|
|
@@ -3284,13 +3552,13 @@ function parseCommandString(input) {
|
|
|
3284
3552
|
function resolvePromptPath(config, cwd) {
|
|
3285
3553
|
var _a2;
|
|
3286
3554
|
const configured = ((_a2 = config.lastPromptPath) == null ? void 0 : _a2.trim()) || ".runtrim/latest-prompt.md";
|
|
3287
|
-
return
|
|
3555
|
+
return path10.isAbsolute(configured) ? configured : path10.join(cwd, configured);
|
|
3288
3556
|
}
|
|
3289
3557
|
function writeLatestPromptFile(content, config, cwd) {
|
|
3290
3558
|
const promptPath = resolvePromptPath(config, cwd);
|
|
3291
|
-
const dir =
|
|
3292
|
-
if (!
|
|
3293
|
-
|
|
3559
|
+
const dir = path10.dirname(promptPath);
|
|
3560
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
3561
|
+
fs10.writeFileSync(promptPath, content, "utf-8");
|
|
3294
3562
|
return promptPath;
|
|
3295
3563
|
}
|
|
3296
3564
|
async function openInEditor(editorValue, config, filePath, cwd) {
|
|
@@ -3342,14 +3610,6 @@ async function copyToClipboardSafe(value) {
|
|
|
3342
3610
|
return false;
|
|
3343
3611
|
}
|
|
3344
3612
|
}
|
|
3345
|
-
function resolveSyncEndpoint(dashboardUrl) {
|
|
3346
|
-
try {
|
|
3347
|
-
const u = new URL(dashboardUrl);
|
|
3348
|
-
return `${u.origin}/api/sync`;
|
|
3349
|
-
} catch (e) {
|
|
3350
|
-
return "http://localhost:3000/api/sync";
|
|
3351
|
-
}
|
|
3352
|
-
}
|
|
3353
3613
|
function parseMemorySection2(memory, title) {
|
|
3354
3614
|
const lines = memory.split(/\r?\n/);
|
|
3355
3615
|
const idx = lines.findIndex((line) => line.trim().toLowerCase() === `${title.toLowerCase()}:`);
|
|
@@ -3470,7 +3730,7 @@ function normalizeContinuationAgent(value, fallback) {
|
|
|
3470
3730
|
return "custom";
|
|
3471
3731
|
}
|
|
3472
3732
|
function resolveContinuationPath(cwd) {
|
|
3473
|
-
return
|
|
3733
|
+
return path10.join(getConfigDir(cwd), "continuation-prompt.md");
|
|
3474
3734
|
}
|
|
3475
3735
|
function extractMemoryValue(memory, label) {
|
|
3476
3736
|
var _a2;
|
|
@@ -3636,8 +3896,8 @@ async function initializeRunTrim(cwd, options = {}) {
|
|
|
3636
3896
|
spinner.stop();
|
|
3637
3897
|
const configDir = getConfigDir(cwd);
|
|
3638
3898
|
const runsDir = getRunsDir(cwd);
|
|
3639
|
-
if (!
|
|
3640
|
-
if (!
|
|
3899
|
+
if (!fs10.existsSync(configDir)) fs10.mkdirSync(configDir, { recursive: true });
|
|
3900
|
+
if (!fs10.existsSync(runsDir)) fs10.mkdirSync(runsDir, { recursive: true });
|
|
3641
3901
|
const existingConfig = hadConfig ? loadConfig(cwd) : null;
|
|
3642
3902
|
const baseConfig = __spreadValues(__spreadValues({}, DEFAULT_CONFIG), detectProjectInfo(cwd));
|
|
3643
3903
|
const nextConfig = options.refresh && existingConfig ? __spreadValues({}, existingConfig) : baseConfig;
|
|
@@ -3648,20 +3908,20 @@ async function initializeRunTrim(cwd, options = {}) {
|
|
|
3648
3908
|
saveConfig(nextConfig, cwd);
|
|
3649
3909
|
writeProjectAudit(baseline, cwd);
|
|
3650
3910
|
writeRules(baseline, cwd);
|
|
3651
|
-
const memoryPath =
|
|
3911
|
+
const memoryPath = path10.join(getConfigDir(cwd), "memory.md");
|
|
3652
3912
|
const hasRuns = loadAllRuns(cwd).length > 0;
|
|
3653
3913
|
if (hasRuns) {
|
|
3654
3914
|
const latest = loadLatestRun(cwd);
|
|
3655
3915
|
if (latest) writeMemoryFromRuns(latest, loadAllRuns(cwd), nextConfig, cwd);
|
|
3656
3916
|
} else {
|
|
3657
|
-
|
|
3917
|
+
fs10.writeFileSync(memoryPath, buildBaselineMemoryMarkdown(baseline), "utf-8");
|
|
3658
3918
|
}
|
|
3659
3919
|
ensureStarterPromptIfMissing(cwd);
|
|
3660
|
-
const gitignorePath =
|
|
3661
|
-
if (
|
|
3662
|
-
const content =
|
|
3920
|
+
const gitignorePath = path10.join(cwd, ".gitignore");
|
|
3921
|
+
if (fs10.existsSync(gitignorePath)) {
|
|
3922
|
+
const content = fs10.readFileSync(gitignorePath, "utf-8");
|
|
3663
3923
|
if (!content.includes(".runtrim/runs")) {
|
|
3664
|
-
|
|
3924
|
+
fs10.appendFileSync(gitignorePath, "\n# RunTrim run history\n.runtrim/runs/\n");
|
|
3665
3925
|
}
|
|
3666
3926
|
}
|
|
3667
3927
|
return { ok: true };
|
|
@@ -3758,8 +4018,8 @@ async function runPrepareTask(task, options) {
|
|
|
3758
4018
|
}
|
|
3759
4019
|
async function tryLaunchPanelMonitorDetached(cwd) {
|
|
3760
4020
|
var _a2, _b;
|
|
3761
|
-
const entry = (_a2 = resolveCliLauncherPath()) != null ? _a2 :
|
|
3762
|
-
if (!
|
|
4021
|
+
const entry = (_a2 = resolveCliLauncherPath()) != null ? _a2 : path10.resolve(cwd, "dist-cli", "runtrim.cjs");
|
|
4022
|
+
if (!fs10.existsSync(entry)) return false;
|
|
3763
4023
|
try {
|
|
3764
4024
|
const child = execa3(process.execPath, [entry, "panel", "--monitor"], {
|
|
3765
4025
|
cwd,
|
|
@@ -3920,7 +4180,7 @@ program.command("init").description("Initialize RunTrim in the current project")
|
|
|
3920
4180
|
if (!initResult.ok) return;
|
|
3921
4181
|
const baseline = (_a2 = loadProjectAudit(cwd)) != null ? _a2 : performBaselineProjectAudit(cwd, null);
|
|
3922
4182
|
const scriptNames = Object.keys(baseline.scripts);
|
|
3923
|
-
const starterCreated =
|
|
4183
|
+
const starterCreated = fs10.existsSync(path10.join(getConfigDir(cwd), "latest-prompt.md"));
|
|
3924
4184
|
console.log(ACCENT.bold(" RunTrim init"));
|
|
3925
4185
|
console.log("");
|
|
3926
4186
|
console.log(DIM(" Project detected"));
|
|
@@ -4538,8 +4798,8 @@ program.command("run <task>").description("Guard then run configured local agent
|
|
|
4538
4798
|
console.log("");
|
|
4539
4799
|
return;
|
|
4540
4800
|
}
|
|
4541
|
-
const outputPath =
|
|
4542
|
-
|
|
4801
|
+
const outputPath = path10.join(getRunsDir(cwd), `${run.id}.output.txt`);
|
|
4802
|
+
fs10.writeFileSync(outputPath, `# stdout
|
|
4543
4803
|
${stdout}
|
|
4544
4804
|
|
|
4545
4805
|
# stderr
|
|
@@ -4616,7 +4876,8 @@ program.command("prepare <task>").description("Prepare a guarded prompt without
|
|
|
4616
4876
|
});
|
|
4617
4877
|
}
|
|
4618
4878
|
);
|
|
4619
|
-
program.command("go <task>").description("
|
|
4879
|
+
program.command("go <task>").description("Bridge Mode: generate a scoped contract, write protocol files, and prepare the guarded prompt").option("--no-clipboard", "Print prompt to terminal instead of copying to clipboard").option("--no-sync", "Skip cloud sync even if a CLI token is configured").option("--no-bridge", "Skip bridge file writing (RUNTRIM.md, contracts, memory)").option("--print", "Always print the prompt to terminal in addition to copying").option("--monitor", "Open local panel monitor in the background (best effort)").action(async (task, options) => {
|
|
4880
|
+
var _a2, _b, _c, _d;
|
|
4620
4881
|
const cwd = process.cwd();
|
|
4621
4882
|
const allowed = await ensureRepoAllowedForFree(cwd);
|
|
4622
4883
|
if (!allowed) return;
|
|
@@ -4624,45 +4885,148 @@ program.command("go <task>").description("Daily shortcut: initialize if needed,
|
|
|
4624
4885
|
const initResult = await initializeRunTrim(cwd, { allowOverwritePrompt: false });
|
|
4625
4886
|
if (!initResult.ok) return;
|
|
4626
4887
|
}
|
|
4627
|
-
const originalLog = console.log;
|
|
4628
|
-
const originalError = console.error;
|
|
4629
|
-
console.log = () => void 0;
|
|
4630
|
-
console.error = () => void 0;
|
|
4631
|
-
try {
|
|
4632
|
-
await runPrepareTask(task, { showHeader: false, copy: true });
|
|
4633
|
-
} finally {
|
|
4634
|
-
console.log = originalLog;
|
|
4635
|
-
console.error = originalError;
|
|
4636
|
-
}
|
|
4637
|
-
if (!configExists(cwd)) return;
|
|
4638
4888
|
const config = loadConfig(cwd);
|
|
4639
|
-
const
|
|
4640
|
-
|
|
4641
|
-
const
|
|
4642
|
-
|
|
4643
|
-
|
|
4889
|
+
const auditSpinner = oraFactory({ text: " Auditing task...", color: "blue" }).start();
|
|
4890
|
+
await new Promise((r) => setTimeout(r, 180));
|
|
4891
|
+
const audit = auditTask(task, config, cwd);
|
|
4892
|
+
auditSpinner.stop();
|
|
4893
|
+
const contract = generateContract(task, audit, config);
|
|
4894
|
+
if (contract.isBlocked && contract.splitReport) {
|
|
4895
|
+
const sr = contract.splitReport;
|
|
4896
|
+
updateRun(saveRun(task, audit, contract, cwd).id, { status: "blocked" }, cwd);
|
|
4897
|
+
console.log("");
|
|
4898
|
+
console.log(GO_ACCENT.bold("RunTrim go"));
|
|
4899
|
+
console.log("");
|
|
4900
|
+
console.log(chalk.red.bold(" SPLIT REQUIRED"));
|
|
4901
|
+
console.log("");
|
|
4902
|
+
console.log(DIM(" Task ") + chalk.white(truncate(task, 60)));
|
|
4903
|
+
console.log(DIM(" Risk ") + chalk.red("CRITICAL"));
|
|
4904
|
+
console.log("");
|
|
4905
|
+
console.log(DIM(" This task crosses multiple high-risk systems."));
|
|
4906
|
+
console.log(DIM(" Running it in one agent session would cause scope drift and token waste."));
|
|
4907
|
+
console.log("");
|
|
4908
|
+
console.log(DIM(" Detected: ") + chalk.white(sr.detectedSystems.join(", ")));
|
|
4909
|
+
console.log("");
|
|
4910
|
+
for (const step of sr.recommendedSplit) {
|
|
4911
|
+
console.log(DIM(" ") + chalk.white(step));
|
|
4912
|
+
}
|
|
4913
|
+
console.log("");
|
|
4914
|
+
console.log(DIM(" Estimated waste avoided: ") + ACCENT(sr.estimatedWasteAvoided));
|
|
4915
|
+
console.log("");
|
|
4916
|
+
return;
|
|
4917
|
+
}
|
|
4918
|
+
const runs = loadAllRuns(cwd);
|
|
4919
|
+
const projectAudit = loadProjectAudit(cwd);
|
|
4920
|
+
const projectName = (_a2 = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _a2 : path10.basename(cwd);
|
|
4921
|
+
const memoryMarkdown = (() => {
|
|
4922
|
+
try {
|
|
4923
|
+
return readMemory(cwd);
|
|
4924
|
+
} catch (e) {
|
|
4925
|
+
return null;
|
|
4926
|
+
}
|
|
4927
|
+
})();
|
|
4928
|
+
const memoryUsed = Boolean(memoryMarkdown && memoryMarkdown.trim().length > 50);
|
|
4929
|
+
const run = saveRun(task, audit, contract, cwd);
|
|
4930
|
+
updateRun(run.id, {
|
|
4931
|
+
status: "guarded",
|
|
4932
|
+
bridgeMode: true,
|
|
4933
|
+
tokenBudget: deriveBridgeContext(task, contract, runs, projectName).tokenBudget,
|
|
4934
|
+
memoryUsed
|
|
4935
|
+
}, cwd);
|
|
4936
|
+
const bridgeCtx = deriveBridgeContext(task, contract, runs, projectName);
|
|
4937
|
+
let bridgeWritten = [];
|
|
4938
|
+
let bridgeManagedPaths = [];
|
|
4939
|
+
if (options.bridge !== false) {
|
|
4940
|
+
const result = writeBridgeFiles(bridgeCtx, cwd);
|
|
4941
|
+
bridgeWritten = result.written;
|
|
4942
|
+
bridgeManagedPaths = result.managedPaths;
|
|
4943
|
+
}
|
|
4944
|
+
const rawPrompt = contract.contractText;
|
|
4945
|
+
const fullPrompt = options.bridge !== false ? buildBridgePrompt(rawPrompt, bridgeCtx) : rawPrompt;
|
|
4946
|
+
const promptPath = writeLatestPromptFile(fullPrompt, config, cwd);
|
|
4947
|
+
const promptRelative = ".runtrim/latest-prompt.md";
|
|
4948
|
+
if (!bridgeManagedPaths.includes(promptRelative)) {
|
|
4949
|
+
bridgeManagedPaths.push(promptRelative);
|
|
4950
|
+
}
|
|
4951
|
+
const doCopy = options.clipboard !== false;
|
|
4952
|
+
const copied = doCopy ? await copyToClipboardSafe(fullPrompt) : false;
|
|
4953
|
+
if (options.monitor) void tryLaunchPanelMonitorDetached(cwd);
|
|
4954
|
+
updateRun(run.id, { bridgeManagedFiles: bridgeManagedPaths }, cwd);
|
|
4955
|
+
let synced = false;
|
|
4956
|
+
if (options.sync !== false) {
|
|
4957
|
+
const globalAuth = loadGlobalAuth();
|
|
4958
|
+
const rawToken = (_c = (_b = globalAuth == null ? void 0 : globalAuth.token) != null ? _b : config.syncToken) != null ? _c : null;
|
|
4959
|
+
if (rawToken == null ? void 0 : rawToken.startsWith("rt_live_")) {
|
|
4960
|
+
try {
|
|
4961
|
+
const freshRuns = loadAllRuns(cwd);
|
|
4962
|
+
const payload = buildSyncPayload({
|
|
4963
|
+
cwd,
|
|
4964
|
+
projectName,
|
|
4965
|
+
config,
|
|
4966
|
+
projectAudit: projectAudit != null ? projectAudit : null,
|
|
4967
|
+
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
4968
|
+
runs: freshRuns
|
|
4969
|
+
});
|
|
4970
|
+
const apiBase = resolveApiBase(config);
|
|
4971
|
+
const r = await fetch(`${apiBase}/api/sync`, {
|
|
4972
|
+
method: "POST",
|
|
4973
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${rawToken}` },
|
|
4974
|
+
body: JSON.stringify(payload)
|
|
4975
|
+
});
|
|
4976
|
+
synced = r.ok;
|
|
4977
|
+
} catch (e) {
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4644
4980
|
}
|
|
4981
|
+
const riskColor = (_d = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[bridgeCtx.riskLevel]) != null ? _d : chalk.white;
|
|
4645
4982
|
console.log("");
|
|
4646
4983
|
console.log(GO_ACCENT.bold("RunTrim go"));
|
|
4647
4984
|
console.log("");
|
|
4648
4985
|
console.log(GO_ACCENT.bold("Task"));
|
|
4649
|
-
console.log(chalk.white(task));
|
|
4986
|
+
console.log(chalk.white(" " + task));
|
|
4650
4987
|
console.log("");
|
|
4651
|
-
console.log(GO_ACCENT.bold("
|
|
4652
|
-
console.log(
|
|
4988
|
+
console.log(GO_ACCENT.bold("Memory"));
|
|
4989
|
+
console.log(DIM(" " + (memoryUsed ? `Loaded ${runs.length} prior run${runs.length === 1 ? "" : "s"} and project context.` : "No prior runs. Starting from project context.")));
|
|
4653
4990
|
console.log("");
|
|
4654
|
-
console.log(GO_ACCENT.bold("
|
|
4655
|
-
console.log(
|
|
4656
|
-
console.log(chalk.white("
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4991
|
+
console.log(GO_ACCENT.bold("Contract"));
|
|
4992
|
+
console.log(DIM(" Risk ") + riskColor(bridgeCtx.riskLevel));
|
|
4993
|
+
console.log(DIM(" Token budget ") + chalk.white("~" + bridgeCtx.tokenBudget.toLocaleString()));
|
|
4994
|
+
if (bridgeCtx.allowedScope.length > 0) {
|
|
4995
|
+
console.log(DIM(" Allowed ") + chalk.white(truncate(bridgeCtx.allowedScope.slice(0, 2).join(", "), 60)));
|
|
4996
|
+
}
|
|
4997
|
+
if (bridgeCtx.forbiddenScope.length > 0) {
|
|
4998
|
+
console.log(DIM(" Forbidden ") + chalk.white(truncate(bridgeCtx.forbiddenScope.slice(0, 2).join(", "), 60)));
|
|
4999
|
+
}
|
|
5000
|
+
console.log(DIM(" Run saved ") + chalk.white(`.runtrim/runs/${run.id}.json`));
|
|
4660
5001
|
console.log("");
|
|
4661
|
-
|
|
4662
|
-
|
|
5002
|
+
if (bridgeWritten.length > 0) {
|
|
5003
|
+
console.log(GO_ACCENT.bold("Bridge"));
|
|
5004
|
+
for (const f of bridgeWritten) {
|
|
5005
|
+
console.log(DIM(" ") + chalk.white(f));
|
|
5006
|
+
}
|
|
5007
|
+
console.log("");
|
|
5008
|
+
}
|
|
5009
|
+
if (options.sync !== false) {
|
|
5010
|
+
console.log(GO_ACCENT.bold("Cloud sync"));
|
|
5011
|
+
console.log(DIM(" ") + (synced ? chalk.white("Synced.") : DIM("Skipped. Run runtrim login to connect your dashboard.")));
|
|
5012
|
+
console.log("");
|
|
5013
|
+
}
|
|
5014
|
+
console.log(GO_ACCENT.bold("Prompt"));
|
|
5015
|
+
if (copied) {
|
|
5016
|
+
console.log(chalk.white(" Copied to clipboard."));
|
|
5017
|
+
} else if (!doCopy) {
|
|
5018
|
+
console.log(chalk.white(" Saved to: " + promptPath));
|
|
5019
|
+
} else {
|
|
5020
|
+
console.log(DIM(" Clipboard unavailable. Saved to: " + promptPath));
|
|
5021
|
+
}
|
|
5022
|
+
if (options.print) {
|
|
5023
|
+
console.log("");
|
|
5024
|
+
console.log(fullPrompt);
|
|
5025
|
+
}
|
|
4663
5026
|
console.log("");
|
|
4664
|
-
console.log(GO_ACCENT.bold("
|
|
4665
|
-
console.log(chalk.white("
|
|
5027
|
+
console.log(GO_ACCENT.bold("Next"));
|
|
5028
|
+
console.log(chalk.white(" Paste the guarded prompt into Claude Code, Codex, Cursor, or your agent."));
|
|
5029
|
+
console.log(chalk.white(" After edits are done, run: runtrim finish"));
|
|
4666
5030
|
console.log("");
|
|
4667
5031
|
});
|
|
4668
5032
|
program.command("check").description("Check the latest run and evaluate agent output").option("--json", "Print machine-readable check summary").action(async (options) => {
|
|
@@ -4828,80 +5192,6 @@ program.command("check").description("Check the latest run and evaluate agent ou
|
|
|
4828
5192
|
console.log(chalk.white(checkSummary.nextSafeAction));
|
|
4829
5193
|
console.log("");
|
|
4830
5194
|
});
|
|
4831
|
-
program.command("sync").description("Sync local RunTrim metadata to dashboard").action(async () => {
|
|
4832
|
-
var _a2, _b;
|
|
4833
|
-
const cwd = process.cwd();
|
|
4834
|
-
const allowed = await ensureRepoAllowedForFree(cwd);
|
|
4835
|
-
if (!allowed) return;
|
|
4836
|
-
const cfg = configExists(cwd) ? loadConfig(cwd) : DEFAULT_CONFIG;
|
|
4837
|
-
if (!configExists(cwd)) {
|
|
4838
|
-
console.log(chalk.yellow(" No config found. Run: runtrim init"));
|
|
4839
|
-
console.log("");
|
|
4840
|
-
return;
|
|
4841
|
-
}
|
|
4842
|
-
const config = loadConfig(cwd);
|
|
4843
|
-
if (!config.syncToken) {
|
|
4844
|
-
console.log(chalk.yellow(" Sync token missing. Run: runtrim auth set <token>"));
|
|
4845
|
-
console.log("");
|
|
4846
|
-
return;
|
|
4847
|
-
}
|
|
4848
|
-
const runs = loadAllRuns(cwd);
|
|
4849
|
-
const latestRun = (_a2 = runs[0]) != null ? _a2 : null;
|
|
4850
|
-
const audit = loadProjectAudit(cwd);
|
|
4851
|
-
const inferredProjectName = (audit == null ? void 0 : audit.projectName) || path9.basename(cwd);
|
|
4852
|
-
let memory = readMemory(cwd);
|
|
4853
|
-
if (!memory) {
|
|
4854
|
-
if (latestRun) {
|
|
4855
|
-
memory = writeMemoryFromRuns(latestRun, runs, config, cwd);
|
|
4856
|
-
} else if (audit) {
|
|
4857
|
-
memory = buildBaselineMemoryMarkdown(audit);
|
|
4858
|
-
fs9.writeFileSync(path9.join(getConfigDir(cwd), "memory.md"), memory, "utf-8");
|
|
4859
|
-
} else {
|
|
4860
|
-
memory = "RunTrim Project Memory\n\nCurrent state:\nNo local runs yet.\n";
|
|
4861
|
-
}
|
|
4862
|
-
}
|
|
4863
|
-
const payload = buildSyncPayload({
|
|
4864
|
-
cwd,
|
|
4865
|
-
projectName: inferredProjectName,
|
|
4866
|
-
config,
|
|
4867
|
-
projectAudit: audit,
|
|
4868
|
-
memoryMarkdown: memory,
|
|
4869
|
-
runs
|
|
4870
|
-
});
|
|
4871
|
-
const endpoint = resolveSyncEndpoint(config.dashboardUrl);
|
|
4872
|
-
try {
|
|
4873
|
-
const response = await fetch(endpoint, {
|
|
4874
|
-
method: "POST",
|
|
4875
|
-
headers: {
|
|
4876
|
-
"content-type": "application/json",
|
|
4877
|
-
"x-runtrim-sync-token": config.syncToken
|
|
4878
|
-
},
|
|
4879
|
-
body: JSON.stringify(payload)
|
|
4880
|
-
});
|
|
4881
|
-
const body = await response.json().catch(() => ({}));
|
|
4882
|
-
if (!response.ok) {
|
|
4883
|
-
console.log(chalk.red(" Sync failed: " + (body.error || `HTTP ${response.status}`)));
|
|
4884
|
-
console.log(DIM(" Endpoint: ") + chalk.white(endpoint));
|
|
4885
|
-
if (Array.isArray(body.missing) && body.missing.length > 0) {
|
|
4886
|
-
console.log(DIM(" Missing: ") + chalk.white(body.missing.join(", ")));
|
|
4887
|
-
}
|
|
4888
|
-
console.log("");
|
|
4889
|
-
return;
|
|
4890
|
-
}
|
|
4891
|
-
const syncedRuns = (_b = body.syncedRuns) != null ? _b : payload.runs.length;
|
|
4892
|
-
console.log(ACCENT.bold(` Synced project memory and ${syncedRuns} runs.`));
|
|
4893
|
-
console.log(DIM(" Open dashboard: ") + chalk.white(config.dashboardUrl));
|
|
4894
|
-
console.log("");
|
|
4895
|
-
} catch (error) {
|
|
4896
|
-
const message = error instanceof Error ? error.message : "Unknown network error";
|
|
4897
|
-
console.log(chalk.red(" Sync failed: " + message));
|
|
4898
|
-
console.log(DIM(" Endpoint: ") + chalk.white(endpoint));
|
|
4899
|
-
console.log(
|
|
4900
|
-
DIM(" If your dashboard backend is offline, start it first and ensure env vars are set.")
|
|
4901
|
-
);
|
|
4902
|
-
console.log("");
|
|
4903
|
-
}
|
|
4904
|
-
});
|
|
4905
5195
|
program.command("audit").description("Run baseline project audit and refresh local baseline files").action(() => {
|
|
4906
5196
|
const cwd = process.cwd();
|
|
4907
5197
|
if (!configExists(cwd)) {
|
|
@@ -4925,7 +5215,7 @@ program.command("audit").description("Run baseline project audit and refresh loc
|
|
|
4925
5215
|
const latest = loadLatestRun(cwd);
|
|
4926
5216
|
if (latest) writeMemoryFromRuns(latest, runs, nextConfig, cwd);
|
|
4927
5217
|
} else {
|
|
4928
|
-
|
|
5218
|
+
fs10.writeFileSync(path10.join(getConfigDir(cwd), "memory.md"), buildBaselineMemoryMarkdown(baseline), "utf-8");
|
|
4929
5219
|
}
|
|
4930
5220
|
console.log("");
|
|
4931
5221
|
console.log(BOLD("RunTrim") + DIM(" audit"));
|
|
@@ -5057,15 +5347,15 @@ program.command("memory").description("Show project memory and latest next safe
|
|
|
5057
5347
|
console.log("");
|
|
5058
5348
|
const config = configExists(cwd) ? loadConfig(cwd) : DEFAULT_CONFIG;
|
|
5059
5349
|
const audit = (_a2 = loadProjectAudit(cwd)) != null ? _a2 : performBaselineProjectAudit(cwd, null);
|
|
5060
|
-
const memoryPath =
|
|
5350
|
+
const memoryPath = path10.join(getConfigDir(cwd), "memory.md");
|
|
5061
5351
|
const latestRun = loadLatestRun(cwd);
|
|
5062
5352
|
if (!latestRun) {
|
|
5063
5353
|
let memory2 = readMemory(cwd);
|
|
5064
5354
|
if (!memory2) {
|
|
5065
|
-
const memoryDir =
|
|
5066
|
-
if (!
|
|
5355
|
+
const memoryDir = path10.dirname(memoryPath);
|
|
5356
|
+
if (!fs10.existsSync(memoryDir)) fs10.mkdirSync(memoryDir, { recursive: true });
|
|
5067
5357
|
memory2 = buildBaselineMemoryMarkdown(audit);
|
|
5068
|
-
|
|
5358
|
+
fs10.writeFileSync(memoryPath, memory2, "utf-8");
|
|
5069
5359
|
}
|
|
5070
5360
|
const baselinePrompt = 'runtrim go "your task"';
|
|
5071
5361
|
if (options.prompt) {
|
|
@@ -5206,13 +5496,13 @@ program.command("continue").description("Create a safe continuation prompt from
|
|
|
5206
5496
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
5207
5497
|
const continuationPath = resolveContinuationPath(cwd);
|
|
5208
5498
|
const latestPromptPath = resolvePromptPath(config, cwd);
|
|
5209
|
-
const latestPrompt =
|
|
5499
|
+
const latestPrompt = fs10.existsSync(latestPromptPath) ? fs10.readFileSync(latestPromptPath, "utf-8").trim() : "";
|
|
5210
5500
|
let memory = readMemory(cwd);
|
|
5211
5501
|
if (!memory) {
|
|
5212
5502
|
if (latestRun) memory = writeMemoryFromRuns(latestRun, allRuns, config, cwd);
|
|
5213
5503
|
else memory = buildBaselineMemoryMarkdown(audit);
|
|
5214
5504
|
}
|
|
5215
|
-
const projectName = audit.projectName ||
|
|
5505
|
+
const projectName = audit.projectName || path10.basename(cwd);
|
|
5216
5506
|
const stackLine = audit.detectedStack.length > 0 ? audit.detectedStack.join(", ") : "unknown";
|
|
5217
5507
|
const diffFiles = dedupeFiles(await getGitDiff(cwd));
|
|
5218
5508
|
const changedFiles = ((_c = (_b = latestRun == null ? void 0 : latestRun.evaluation) == null ? void 0 : _b.changedFiles) == null ? void 0 : _c.length) ? dedupeFiles(latestRun.evaluation.changedFiles) : diffFiles;
|
|
@@ -5388,13 +5678,13 @@ program.command("continue").description("Create a safe continuation prompt from
|
|
|
5388
5678
|
promptLines.push("");
|
|
5389
5679
|
}
|
|
5390
5680
|
const continuationPrompt = promptLines.join("\n");
|
|
5391
|
-
const continuationDir =
|
|
5392
|
-
if (!
|
|
5393
|
-
|
|
5681
|
+
const continuationDir = path10.dirname(continuationPath);
|
|
5682
|
+
if (!fs10.existsSync(continuationDir)) fs10.mkdirSync(continuationDir, { recursive: true });
|
|
5683
|
+
fs10.writeFileSync(continuationPath, continuationPrompt, "utf-8");
|
|
5394
5684
|
const copied = await copyToClipboardSafe(continuationPrompt);
|
|
5395
5685
|
if (memory) {
|
|
5396
5686
|
const memoryWithContinuation = updateMemoryWithContinuation(memory, reason, continuationPath, nowIso);
|
|
5397
|
-
|
|
5687
|
+
fs10.writeFileSync(path10.join(getConfigDir(cwd), "memory.md"), memoryWithContinuation, "utf-8");
|
|
5398
5688
|
}
|
|
5399
5689
|
if (hasConfig) {
|
|
5400
5690
|
const nextConfig = __spreadProps(__spreadValues({}, config), {
|
|
@@ -5505,7 +5795,7 @@ program.command("report").description("Show a summary of all local RunTrim runs"
|
|
|
5505
5795
|
console.log(DIM(" " + SECTION));
|
|
5506
5796
|
console.log("");
|
|
5507
5797
|
const audit = loadProjectAudit(cwd);
|
|
5508
|
-
console.log(DIM(" Project ") + chalk.white((_b = audit == null ? void 0 : audit.projectName) != null ? _b :
|
|
5798
|
+
console.log(DIM(" Project ") + chalk.white((_b = audit == null ? void 0 : audit.projectName) != null ? _b : path10.basename(cwd)));
|
|
5509
5799
|
console.log(DIM(" Focus ") + chalk.white(truncate((_d = (_c = latestRun.contract.contract) == null ? void 0 : _c.cleanedObjective) != null ? _d : latestRun.task, 58)));
|
|
5510
5800
|
console.log(DIM(" Status ") + chalk.white(formatStatus((_e = latestEval == null ? void 0 : latestEval.status) != null ? _e : latestRun.status)));
|
|
5511
5801
|
console.log(DIM(" Changed ") + chalk.white(`${(_g = (_f = latestEval == null ? void 0 : latestEval.changedFiles) == null ? void 0 : _f.length) != null ? _g : 0} file${((_i = (_h = latestEval == null ? void 0 : latestEval.changedFiles) == null ? void 0 : _h.length) != null ? _i : 0) === 1 ? "" : "s"}`));
|
|
@@ -5568,4 +5858,389 @@ var statusColors = {
|
|
|
5568
5858
|
drift_detected: chalk.red,
|
|
5569
5859
|
blocked: chalk.red
|
|
5570
5860
|
};
|
|
5861
|
+
var GLOBAL_AUTH_DIR = path10.join(os3.homedir(), ".runtrim");
|
|
5862
|
+
var GLOBAL_AUTH_FILE = path10.join(GLOBAL_AUTH_DIR, "auth.json");
|
|
5863
|
+
function loadGlobalAuth() {
|
|
5864
|
+
if (!fs10.existsSync(GLOBAL_AUTH_FILE)) return null;
|
|
5865
|
+
try {
|
|
5866
|
+
return JSON.parse(fs10.readFileSync(GLOBAL_AUTH_FILE, "utf-8"));
|
|
5867
|
+
} catch (e) {
|
|
5868
|
+
return null;
|
|
5869
|
+
}
|
|
5870
|
+
}
|
|
5871
|
+
function saveGlobalAuth(auth) {
|
|
5872
|
+
if (!fs10.existsSync(GLOBAL_AUTH_DIR)) {
|
|
5873
|
+
fs10.mkdirSync(GLOBAL_AUTH_DIR, { recursive: true });
|
|
5874
|
+
}
|
|
5875
|
+
fs10.writeFileSync(GLOBAL_AUTH_FILE, JSON.stringify(auth, null, 2));
|
|
5876
|
+
}
|
|
5877
|
+
function resolveApiBase(config) {
|
|
5878
|
+
var _a2;
|
|
5879
|
+
const url = (_a2 = config.dashboardUrl) == null ? void 0 : _a2.trim();
|
|
5880
|
+
if (!url || url.startsWith("http://localhost")) {
|
|
5881
|
+
return "https://www.runtrim.com";
|
|
5882
|
+
}
|
|
5883
|
+
try {
|
|
5884
|
+
return new URL(url).origin;
|
|
5885
|
+
} catch (e) {
|
|
5886
|
+
return "https://www.runtrim.com";
|
|
5887
|
+
}
|
|
5888
|
+
}
|
|
5889
|
+
program.command("login").description("Connect this machine to your RunTrim cloud account").option("--token <token>", "CLI token (skip interactive prompt)").action(async (opts) => {
|
|
5890
|
+
var _a2, _b, _c, _d, _e;
|
|
5891
|
+
console.log("");
|
|
5892
|
+
console.log(BOLD("RunTrim") + DIM(" connect to cloud"));
|
|
5893
|
+
console.log("");
|
|
5894
|
+
const cwd = process.cwd();
|
|
5895
|
+
const existing = loadGlobalAuth();
|
|
5896
|
+
if (existing && !opts.token) {
|
|
5897
|
+
console.log(DIM(" Already connected."));
|
|
5898
|
+
if (existing.email) {
|
|
5899
|
+
console.log(DIM(" Account ") + chalk.white(existing.email));
|
|
5900
|
+
}
|
|
5901
|
+
console.log(DIM(" Token ") + chalk.white("rt_live_..."));
|
|
5902
|
+
console.log("");
|
|
5903
|
+
console.log(DIM(" To reconnect, run: ") + GO_ACCENT("runtrim login --token <new-token>"));
|
|
5904
|
+
console.log("");
|
|
5905
|
+
return;
|
|
5906
|
+
}
|
|
5907
|
+
console.log(DIM(" Get your CLI token from:"));
|
|
5908
|
+
console.log(" " + GO_ACCENT("https://www.runtrim.com/app/connect"));
|
|
5909
|
+
console.log("");
|
|
5910
|
+
let rawToken = (_b = (_a2 = opts.token) == null ? void 0 : _a2.trim()) != null ? _b : "";
|
|
5911
|
+
if (!rawToken) {
|
|
5912
|
+
const answer = await prompts({
|
|
5913
|
+
type: "text",
|
|
5914
|
+
name: "token",
|
|
5915
|
+
message: "Paste your CLI token"
|
|
5916
|
+
});
|
|
5917
|
+
rawToken = (_d = (_c = answer.token) == null ? void 0 : _c.trim()) != null ? _d : "";
|
|
5918
|
+
}
|
|
5919
|
+
if (!rawToken || !rawToken.startsWith("rt_live_")) {
|
|
5920
|
+
console.log(chalk.yellow(" Invalid token. Tokens start with rt_live_"));
|
|
5921
|
+
console.log("");
|
|
5922
|
+
return;
|
|
5923
|
+
}
|
|
5924
|
+
const config = configExists(cwd) ? loadConfig(cwd) : DEFAULT_CONFIG;
|
|
5925
|
+
const apiBase = resolveApiBase(config);
|
|
5926
|
+
const verifyUrl = `${apiBase}/api/cli-token/verify`;
|
|
5927
|
+
const spinner = oraFactory({ text: " Verifying token...", color: "blue" }).start();
|
|
5928
|
+
let email;
|
|
5929
|
+
try {
|
|
5930
|
+
const res = await fetch(verifyUrl, {
|
|
5931
|
+
method: "GET",
|
|
5932
|
+
headers: { Authorization: `Bearer ${rawToken}` }
|
|
5933
|
+
});
|
|
5934
|
+
const body = await res.json();
|
|
5935
|
+
if (!res.ok || !body.ok) {
|
|
5936
|
+
spinner.fail(" Invalid token. " + ((_e = body.error) != null ? _e : ""));
|
|
5937
|
+
console.log("");
|
|
5938
|
+
return;
|
|
5939
|
+
}
|
|
5940
|
+
email = body.email;
|
|
5941
|
+
spinner.succeed(" Token verified.");
|
|
5942
|
+
} catch (e) {
|
|
5943
|
+
spinner.fail(" Could not reach RunTrim server. Check your connection.");
|
|
5944
|
+
console.log("");
|
|
5945
|
+
return;
|
|
5946
|
+
}
|
|
5947
|
+
saveGlobalAuth({ token: rawToken, storedAt: (/* @__PURE__ */ new Date()).toISOString(), email });
|
|
5948
|
+
console.log("");
|
|
5949
|
+
if (email) {
|
|
5950
|
+
console.log(ACCENT.bold(" Connected as ") + chalk.white(email));
|
|
5951
|
+
} else {
|
|
5952
|
+
console.log(ACCENT.bold(" Connected to RunTrim cloud."));
|
|
5953
|
+
}
|
|
5954
|
+
console.log(DIM(" Token stored in ") + chalk.white("~/.runtrim/auth.json"));
|
|
5955
|
+
console.log("");
|
|
5956
|
+
console.log(DIM(" Next: navigate to a project and run ") + GO_ACCENT("runtrim sync"));
|
|
5957
|
+
console.log("");
|
|
5958
|
+
});
|
|
5959
|
+
program.command("finish").description("Bridge Mode: evaluate agent output, check scope, mark run completed, and sync").option("--no-sync", "Skip cloud sync even if a CLI token is configured").action(async (options) => {
|
|
5960
|
+
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
|
|
5961
|
+
const cwd = process.cwd();
|
|
5962
|
+
console.log("");
|
|
5963
|
+
console.log(GO_ACCENT.bold("RunTrim finish"));
|
|
5964
|
+
console.log("");
|
|
5965
|
+
const allRuns = loadAllRuns(cwd);
|
|
5966
|
+
const activeRun = allRuns.find((r) => r.status === "guarded" || r.status === "checked");
|
|
5967
|
+
if (!activeRun) {
|
|
5968
|
+
console.log(chalk.yellow(" No active RunTrim session found."));
|
|
5969
|
+
console.log(DIM(' Start a new session with: runtrim go "<task>"'));
|
|
5970
|
+
console.log("");
|
|
5971
|
+
return;
|
|
5972
|
+
}
|
|
5973
|
+
const config = configExists(cwd) ? loadConfig(cwd) : DEFAULT_CONFIG;
|
|
5974
|
+
const projectAudit = loadProjectAudit(cwd);
|
|
5975
|
+
const projectName = (_a2 = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _a2 : path10.basename(cwd);
|
|
5976
|
+
console.log(DIM(" Run ") + chalk.white(truncate(activeRun.task, 60)));
|
|
5977
|
+
console.log(DIM(" Run ID ") + chalk.white(activeRun.id));
|
|
5978
|
+
console.log("");
|
|
5979
|
+
const allChangedFiles = dedupeFiles(await getGitDiff(cwd));
|
|
5980
|
+
const sessionManagedFiles = (_b = activeRun.bridgeManagedFiles) != null ? _b : [];
|
|
5981
|
+
function isRuntrimOwned(f) {
|
|
5982
|
+
const norm = f.replace(/\\/g, "/").toLowerCase();
|
|
5983
|
+
if (norm.startsWith(".runtrim/")) return true;
|
|
5984
|
+
if (norm === "runtrim.md") return true;
|
|
5985
|
+
if (sessionManagedFiles.some(
|
|
5986
|
+
(m) => m.replace(/\\/g, "/").toLowerCase() === norm
|
|
5987
|
+
)) return true;
|
|
5988
|
+
return false;
|
|
5989
|
+
}
|
|
5990
|
+
const runtrimFiles = [];
|
|
5991
|
+
const agentFiles = [];
|
|
5992
|
+
for (const f of allChangedFiles) {
|
|
5993
|
+
if (isRuntrimOwned(f)) {
|
|
5994
|
+
runtrimFiles.push(f);
|
|
5995
|
+
} else {
|
|
5996
|
+
agentFiles.push(f);
|
|
5997
|
+
}
|
|
5998
|
+
}
|
|
5999
|
+
const changedFiles = agentFiles;
|
|
6000
|
+
const maxFiles = inferMaxFilesFromScope(
|
|
6001
|
+
(_d = (_c = activeRun.contract.contract) == null ? void 0 : _c.relevantScope) != null ? _d : [],
|
|
6002
|
+
config.maxFilesPerRun
|
|
6003
|
+
);
|
|
6004
|
+
const scope = evaluateWatchState({
|
|
6005
|
+
changedFiles,
|
|
6006
|
+
run: activeRun,
|
|
6007
|
+
maxFilesPerRun: maxFiles,
|
|
6008
|
+
strict: false
|
|
6009
|
+
});
|
|
6010
|
+
const evaluation = evaluateAgentOutput(null, changedFiles, {
|
|
6011
|
+
task: activeRun.task,
|
|
6012
|
+
relevantScope: (_f = (_e = activeRun.contract.contract) == null ? void 0 : _e.relevantScope) != null ? _f : [],
|
|
6013
|
+
sensitiveScope: (_h = (_g = activeRun.contract.contract) == null ? void 0 : _g.sensitiveScope) != null ? _h : [],
|
|
6014
|
+
forbiddenScope: (_j = (_i = activeRun.contract.contract) == null ? void 0 : _i.forbiddenScope) != null ? _j : [],
|
|
6015
|
+
runStatus: activeRun.status
|
|
6016
|
+
});
|
|
6017
|
+
let scopeDriftStatus = "passed";
|
|
6018
|
+
if (scope.forbiddenFiles.length > 0) scopeDriftStatus = "forbidden_touched";
|
|
6019
|
+
else if (scope.outOfScopeFiles.length > 0) scopeDriftStatus = "out_of_scope";
|
|
6020
|
+
else if (evaluation.scopeDriftRisk === "high" || evaluation.scopeDriftRisk === "medium")
|
|
6021
|
+
scopeDriftStatus = "drift_detected";
|
|
6022
|
+
const forbiddenCount = scope.forbiddenFiles.length;
|
|
6023
|
+
const reportParts = [];
|
|
6024
|
+
if (changedFiles.length === 0) {
|
|
6025
|
+
reportParts.push("No agent changes detected.");
|
|
6026
|
+
} else {
|
|
6027
|
+
reportParts.push(`${changedFiles.length} file${changedFiles.length === 1 ? "" : "s"} changed.`);
|
|
6028
|
+
}
|
|
6029
|
+
if (forbiddenCount > 0) {
|
|
6030
|
+
reportParts.push(`${forbiddenCount} forbidden file${forbiddenCount === 1 ? "" : "s"} touched.`);
|
|
6031
|
+
} else if (changedFiles.length > 0) {
|
|
6032
|
+
reportParts.push("No forbidden systems touched.");
|
|
6033
|
+
}
|
|
6034
|
+
if (scopeDriftStatus === "passed" && changedFiles.length > 0) {
|
|
6035
|
+
reportParts.push("Changes within contract.");
|
|
6036
|
+
}
|
|
6037
|
+
if (evaluation.memorySummary) reportParts.push(evaluation.memorySummary);
|
|
6038
|
+
const reportSummary = reportParts.join(" ");
|
|
6039
|
+
const continuationPack = evaluation.nextGuardedPrompt || null;
|
|
6040
|
+
if (continuationPack) {
|
|
6041
|
+
const contDir = getConfigDir(cwd);
|
|
6042
|
+
if (!fs10.existsSync(contDir)) fs10.mkdirSync(contDir, { recursive: true });
|
|
6043
|
+
fs10.writeFileSync(path10.join(contDir, "continuation-prompt.md"), continuationPack, "utf-8");
|
|
6044
|
+
}
|
|
6045
|
+
const evalRecord = __spreadProps(__spreadValues({}, evaluation), {
|
|
6046
|
+
nextPrompt: evaluation.nextGuardedPrompt,
|
|
6047
|
+
nextSafePrompt: evaluation.nextGuardedPrompt,
|
|
6048
|
+
nextSafeAction: evaluation.nextSafeAction,
|
|
6049
|
+
memorySummary: evaluation.memorySummary,
|
|
6050
|
+
evaluatedAt: evaluation.evaluatedAt
|
|
6051
|
+
});
|
|
6052
|
+
updateRun(activeRun.id, {
|
|
6053
|
+
status: "completed",
|
|
6054
|
+
evaluation: evalRecord,
|
|
6055
|
+
scopeDriftStatus,
|
|
6056
|
+
reportSummary,
|
|
6057
|
+
watchStatus: scope.status,
|
|
6058
|
+
watchWarnings: scope.warnings,
|
|
6059
|
+
watchChangedFiles: agentFiles
|
|
6060
|
+
// only agent changes, not RunTrim protocol files
|
|
6061
|
+
}, cwd);
|
|
6062
|
+
const freshRuns = loadAllRuns(cwd);
|
|
6063
|
+
const updatedRun = (_k = freshRuns.find((r) => r.id === activeRun.id)) != null ? _k : activeRun;
|
|
6064
|
+
writeMemoryFromRuns(updatedRun, freshRuns, config, cwd);
|
|
6065
|
+
let synced = false;
|
|
6066
|
+
if (options.sync !== false) {
|
|
6067
|
+
const globalAuth = loadGlobalAuth();
|
|
6068
|
+
const rawToken = (_m = (_l = globalAuth == null ? void 0 : globalAuth.token) != null ? _l : config.syncToken) != null ? _m : null;
|
|
6069
|
+
if (rawToken == null ? void 0 : rawToken.startsWith("rt_live_")) {
|
|
6070
|
+
try {
|
|
6071
|
+
const memoryMarkdown = (() => {
|
|
6072
|
+
try {
|
|
6073
|
+
return readMemory(cwd);
|
|
6074
|
+
} catch (e) {
|
|
6075
|
+
return null;
|
|
6076
|
+
}
|
|
6077
|
+
})();
|
|
6078
|
+
const payload = buildSyncPayload({
|
|
6079
|
+
cwd,
|
|
6080
|
+
projectName,
|
|
6081
|
+
config,
|
|
6082
|
+
projectAudit: projectAudit != null ? projectAudit : null,
|
|
6083
|
+
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
6084
|
+
runs: freshRuns
|
|
6085
|
+
});
|
|
6086
|
+
const apiBase = resolveApiBase(config);
|
|
6087
|
+
const r = await fetch(`${apiBase}/api/sync`, {
|
|
6088
|
+
method: "POST",
|
|
6089
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${rawToken}` },
|
|
6090
|
+
body: JSON.stringify(payload)
|
|
6091
|
+
});
|
|
6092
|
+
synced = r.ok;
|
|
6093
|
+
} catch (e) {
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6097
|
+
const scopeColor = scopeDriftStatus === "passed" ? chalk.green : scopeDriftStatus === "forbidden_touched" ? chalk.red : chalk.yellow;
|
|
6098
|
+
const riskAfter = (_n = activeRun.contract.wasteRiskAfter) != null ? _n : "medium";
|
|
6099
|
+
const riskColor = (_o = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[riskAfter]) != null ? _o : chalk.white;
|
|
6100
|
+
console.log(GO_ACCENT.bold("Run"));
|
|
6101
|
+
console.log(chalk.white(" " + truncate(activeRun.task, 70)));
|
|
6102
|
+
console.log("");
|
|
6103
|
+
if (changedFiles.length > 0) {
|
|
6104
|
+
console.log(GO_ACCENT.bold("Changed files"));
|
|
6105
|
+
for (const f of changedFiles.slice(0, 8)) {
|
|
6106
|
+
const isForbidden = scope.forbiddenFiles.includes(f);
|
|
6107
|
+
const isSensitive = scope.sensitiveFiles.includes(f);
|
|
6108
|
+
const marker = isForbidden ? chalk.red(" [forbidden]") : isSensitive ? chalk.yellow(" [sensitive]") : "";
|
|
6109
|
+
console.log(chalk.white(" - " + f) + marker);
|
|
6110
|
+
}
|
|
6111
|
+
if (changedFiles.length > 8) {
|
|
6112
|
+
console.log(DIM(` ... and ${changedFiles.length - 8} more`));
|
|
6113
|
+
}
|
|
6114
|
+
console.log("");
|
|
6115
|
+
} else {
|
|
6116
|
+
console.log(GO_ACCENT.bold("Changed files"));
|
|
6117
|
+
console.log(DIM(" No agent changes detected."));
|
|
6118
|
+
console.log("");
|
|
6119
|
+
}
|
|
6120
|
+
if (runtrimFiles.length > 0) {
|
|
6121
|
+
console.log(GO_ACCENT.bold("RunTrim files"));
|
|
6122
|
+
for (const f of runtrimFiles) {
|
|
6123
|
+
console.log(DIM(" - " + f));
|
|
6124
|
+
}
|
|
6125
|
+
console.log("");
|
|
6126
|
+
}
|
|
6127
|
+
console.log(GO_ACCENT.bold("Scope"));
|
|
6128
|
+
if (changedFiles.length === 0) {
|
|
6129
|
+
console.log(chalk.green(" No agent changes to evaluate."));
|
|
6130
|
+
} else {
|
|
6131
|
+
console.log(scopeColor(" " + (scopeDriftStatus === "passed" ? "Passed" : scopeDriftStatus === "forbidden_touched" ? "Failed \u2014 forbidden files touched" : "Drift detected")));
|
|
6132
|
+
}
|
|
6133
|
+
console.log("");
|
|
6134
|
+
console.log(GO_ACCENT.bold("Risk"));
|
|
6135
|
+
console.log(riskColor(" " + riskAfter));
|
|
6136
|
+
console.log("");
|
|
6137
|
+
console.log(GO_ACCENT.bold("Report"));
|
|
6138
|
+
console.log(chalk.white(" " + reportSummary));
|
|
6139
|
+
console.log("");
|
|
6140
|
+
if (continuationPack) {
|
|
6141
|
+
console.log(GO_ACCENT.bold("Continuation"));
|
|
6142
|
+
console.log(chalk.white(" Saved to .runtrim/continuation-prompt.md"));
|
|
6143
|
+
console.log("");
|
|
6144
|
+
}
|
|
6145
|
+
if (evaluation.nextSafeAction && evaluation.nextSafeAction !== "Run is ready to continue.") {
|
|
6146
|
+
console.log(GO_ACCENT.bold("Next safest step"));
|
|
6147
|
+
console.log(chalk.white(" " + evaluation.nextSafeAction));
|
|
6148
|
+
console.log("");
|
|
6149
|
+
}
|
|
6150
|
+
if (options.sync !== false) {
|
|
6151
|
+
console.log(GO_ACCENT.bold("Cloud sync"));
|
|
6152
|
+
console.log(DIM(" ") + (synced ? chalk.white("Completed.") : DIM("Skipped. Run runtrim login to connect your dashboard.")));
|
|
6153
|
+
console.log("");
|
|
6154
|
+
}
|
|
6155
|
+
});
|
|
6156
|
+
program.command("sync").description("Sync local run history and project memory to your RunTrim dashboard").option("--dry-run", "Show what would be synced without uploading").action(async (opts) => {
|
|
6157
|
+
var _a2, _b, _c, _d, _e, _f;
|
|
6158
|
+
const cwd = process.cwd();
|
|
6159
|
+
console.log("");
|
|
6160
|
+
console.log(BOLD("RunTrim") + DIM(" cloud sync"));
|
|
6161
|
+
console.log("");
|
|
6162
|
+
const globalAuth = loadGlobalAuth();
|
|
6163
|
+
const config = configExists(cwd) ? loadConfig(cwd) : DEFAULT_CONFIG;
|
|
6164
|
+
const rawToken = (_b = (_a2 = globalAuth == null ? void 0 : globalAuth.token) != null ? _a2 : config.syncToken) != null ? _b : null;
|
|
6165
|
+
if (!rawToken) {
|
|
6166
|
+
console.log(chalk.yellow(" No CLI token found."));
|
|
6167
|
+
console.log(DIM(" Run ") + GO_ACCENT("runtrim login") + DIM(" to connect cloud sync."));
|
|
6168
|
+
console.log(DIM(" Local CLI still works without a token."));
|
|
6169
|
+
console.log("");
|
|
6170
|
+
return;
|
|
6171
|
+
}
|
|
6172
|
+
if (!rawToken.startsWith("rt_live_")) {
|
|
6173
|
+
console.log(chalk.yellow(" Stored token format is invalid. Re-run: runtrim login"));
|
|
6174
|
+
console.log("");
|
|
6175
|
+
return;
|
|
6176
|
+
}
|
|
6177
|
+
const apiBase = resolveApiBase(config);
|
|
6178
|
+
const syncUrl = `${apiBase}/api/sync`;
|
|
6179
|
+
const runs = loadAllRuns(cwd);
|
|
6180
|
+
if (runs.length === 0) {
|
|
6181
|
+
console.log(DIM(" No local runs found in this directory."));
|
|
6182
|
+
console.log(DIM(" Run ") + GO_ACCENT('runtrim go "your task"') + DIM(" first to create runs."));
|
|
6183
|
+
console.log("");
|
|
6184
|
+
return;
|
|
6185
|
+
}
|
|
6186
|
+
const projectAudit = loadProjectAudit(cwd);
|
|
6187
|
+
const memoryMarkdown = (() => {
|
|
6188
|
+
try {
|
|
6189
|
+
return readMemory(cwd);
|
|
6190
|
+
} catch (e) {
|
|
6191
|
+
return "";
|
|
6192
|
+
}
|
|
6193
|
+
})();
|
|
6194
|
+
const payload = buildSyncPayload({
|
|
6195
|
+
cwd,
|
|
6196
|
+
projectName: (_c = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _c : path10.basename(cwd),
|
|
6197
|
+
config,
|
|
6198
|
+
projectAudit: projectAudit != null ? projectAudit : null,
|
|
6199
|
+
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
6200
|
+
runs
|
|
6201
|
+
});
|
|
6202
|
+
console.log(DIM(" Project ") + chalk.white(payload.project.name));
|
|
6203
|
+
console.log(DIM(" Runs ") + chalk.white(String(payload.runs.length)));
|
|
6204
|
+
console.log(DIM(" API ") + chalk.white(syncUrl));
|
|
6205
|
+
console.log("");
|
|
6206
|
+
if (opts.dryRun) {
|
|
6207
|
+
console.log(ACCENT.bold(" Dry run \u2014 nothing uploaded."));
|
|
6208
|
+
console.log("");
|
|
6209
|
+
return;
|
|
6210
|
+
}
|
|
6211
|
+
const spinner = oraFactory({ text: " Syncing...", color: "blue" }).start();
|
|
6212
|
+
try {
|
|
6213
|
+
const res = await fetch(syncUrl, {
|
|
6214
|
+
method: "POST",
|
|
6215
|
+
headers: {
|
|
6216
|
+
"Content-Type": "application/json",
|
|
6217
|
+
Authorization: `Bearer ${rawToken}`
|
|
6218
|
+
},
|
|
6219
|
+
body: JSON.stringify(payload)
|
|
6220
|
+
});
|
|
6221
|
+
const body = await res.json();
|
|
6222
|
+
if (!res.ok || !body.ok) {
|
|
6223
|
+
spinner.fail(" Sync failed.");
|
|
6224
|
+
console.log("");
|
|
6225
|
+
if (body.error) {
|
|
6226
|
+
console.log(chalk.red(" Error: ") + chalk.white(body.error));
|
|
6227
|
+
if (res.status === 401) {
|
|
6228
|
+
console.log(DIM(" Token may be invalid or expired. Run: runtrim login"));
|
|
6229
|
+
}
|
|
6230
|
+
}
|
|
6231
|
+
console.log("");
|
|
6232
|
+
return;
|
|
6233
|
+
}
|
|
6234
|
+
spinner.succeed(" Sync complete.");
|
|
6235
|
+
console.log("");
|
|
6236
|
+
console.log(ACCENT.bold(" Synced") + chalk.white(` ${(_d = body.syncedRuns) != null ? _d : payload.runs.length} run${((_e = body.syncedRuns) != null ? _e : payload.runs.length) === 1 ? "" : "s"}`));
|
|
6237
|
+
console.log(DIM(" Project ID ") + chalk.white((_f = body.projectId) != null ? _f : "\u2014"));
|
|
6238
|
+
console.log("");
|
|
6239
|
+
console.log(DIM(" View at ") + GO_ACCENT(`${apiBase}/app`));
|
|
6240
|
+
console.log("");
|
|
6241
|
+
} catch (err) {
|
|
6242
|
+
spinner.fail(" Network error. Check your connection.");
|
|
6243
|
+
console.log("");
|
|
6244
|
+
}
|
|
6245
|
+
});
|
|
5571
6246
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runtrim",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "CLI guard layer that scopes AI coding runs before they waste tokens.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "RunTrim",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"build:all": "npm run build:cli && npm run build:web",
|
|
39
39
|
"start": "next start",
|
|
40
40
|
"lint": "eslint",
|
|
41
|
+
"prepublishOnly": "npm run build:cli",
|
|
41
42
|
"runtrim": "tsx cli/runtrim.ts",
|
|
42
43
|
"runtrim:build": "npm run build:cli"
|
|
43
44
|
},
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"@radix-ui/react-switch": "^1.2.6",
|
|
55
56
|
"@radix-ui/react-tabs": "^1.1.13",
|
|
56
57
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
58
|
+
"@supabase/ssr": "^0.10.2",
|
|
57
59
|
"@supabase/supabase-js": "^2.57.4",
|
|
58
60
|
"chalk": "^5.6.2",
|
|
59
61
|
"class-variance-authority": "^0.7.1",
|