runtrim 0.1.7 → 0.1.9
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 +1226 -202
- package/package.json +2 -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,
|
|
@@ -3905,13 +4165,274 @@ program.command("start").description("Guided RunTrim onboarding and daily loop")
|
|
|
3905
4165
|
console.log("");
|
|
3906
4166
|
}
|
|
3907
4167
|
});
|
|
3908
|
-
|
|
4168
|
+
var PROTOCOL_BLOCK_START = "<!-- RUNTRIM_PROTOCOL_START -->";
|
|
4169
|
+
var PROTOCOL_BLOCK_END = "<!-- RUNTRIM_PROTOCOL_END -->";
|
|
4170
|
+
var PROTOCOL_POINTER_BLOCK = `
|
|
4171
|
+
${PROTOCOL_BLOCK_START}
|
|
4172
|
+
This repo uses RunTrim as the guarded AI coding protocol.
|
|
4173
|
+
Before editing code, read RUNTRIM.md.
|
|
4174
|
+
Start every task with: runtrim go "<task>"
|
|
4175
|
+
Stay inside .runtrim/contracts/latest.md.
|
|
4176
|
+
After edits, ask the user to run: runtrim finish
|
|
4177
|
+
${PROTOCOL_BLOCK_END}
|
|
4178
|
+
`;
|
|
4179
|
+
function upsertProtocolBlock(filePath) {
|
|
4180
|
+
if (!fs10.existsSync(filePath)) return "skipped";
|
|
4181
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
4182
|
+
const startIdx = content.indexOf(PROTOCOL_BLOCK_START);
|
|
4183
|
+
const endIdx = content.indexOf(PROTOCOL_BLOCK_END);
|
|
4184
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
4185
|
+
const before = content.slice(0, startIdx);
|
|
4186
|
+
const after = content.slice(endIdx + PROTOCOL_BLOCK_END.length);
|
|
4187
|
+
const newContent = before + PROTOCOL_POINTER_BLOCK.trimStart() + after.replace(/^\n/, "");
|
|
4188
|
+
if (newContent === content) return "unchanged";
|
|
4189
|
+
fs10.writeFileSync(filePath, newContent, "utf-8");
|
|
4190
|
+
return "updated";
|
|
4191
|
+
}
|
|
4192
|
+
fs10.writeFileSync(filePath, content.trimEnd() + "\n" + PROTOCOL_POINTER_BLOCK, "utf-8");
|
|
4193
|
+
return "updated";
|
|
4194
|
+
}
|
|
4195
|
+
function createMinimalAgentPointerFile(filePath, filename) {
|
|
4196
|
+
const label = filename === "CLAUDE.md" ? "Claude Code" : "AI agents";
|
|
4197
|
+
const content = [
|
|
4198
|
+
`# ${label} Instructions`,
|
|
4199
|
+
"",
|
|
4200
|
+
"This repo uses RunTrim as the guarded AI coding protocol.",
|
|
4201
|
+
"Read RUNTRIM.md before editing any code.",
|
|
4202
|
+
"",
|
|
4203
|
+
PROTOCOL_POINTER_BLOCK.trim(),
|
|
4204
|
+
""
|
|
4205
|
+
].join("\n");
|
|
4206
|
+
fs10.writeFileSync(filePath, content, "utf-8");
|
|
4207
|
+
}
|
|
4208
|
+
function installProtocol(cwd, baseline, opts = {}) {
|
|
4209
|
+
var _a2, _b, _c, _d, _e, _f, _g;
|
|
4210
|
+
const configDir = getConfigDir(cwd);
|
|
4211
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4212
|
+
const extraFolders = [
|
|
4213
|
+
path10.join(configDir, "contracts"),
|
|
4214
|
+
path10.join(configDir, "memory"),
|
|
4215
|
+
path10.join(configDir, "bridge"),
|
|
4216
|
+
path10.join(configDir, "reports")
|
|
4217
|
+
];
|
|
4218
|
+
const createdFolders = [];
|
|
4219
|
+
for (const dir of extraFolders) {
|
|
4220
|
+
if (!fs10.existsSync(dir)) {
|
|
4221
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
4222
|
+
createdFolders.push(dir.replace(cwd + path10.sep, "").replace(/\\/g, "/"));
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
const scripts = (_a2 = baseline.scripts) != null ? _a2 : {};
|
|
4226
|
+
const buildCmd = (_d = (_c = (_b = scripts["build"]) != null ? _b : scripts["build:web"]) != null ? _c : scripts["build:all"]) != null ? _d : null;
|
|
4227
|
+
const testCmd = (_f = (_e = scripts["test"]) != null ? _e : scripts["test:run"]) != null ? _f : null;
|
|
4228
|
+
const runtrimMdPath = path10.join(cwd, "RUNTRIM.md");
|
|
4229
|
+
const runtrimMdExists = fs10.existsSync(runtrimMdPath);
|
|
4230
|
+
const runtrimMd = [
|
|
4231
|
+
"# RunTrim Protocol",
|
|
4232
|
+
"",
|
|
4233
|
+
"This repo uses RunTrim as the guarded AI coding control layer.",
|
|
4234
|
+
"",
|
|
4235
|
+
"## Starting an AI coding task",
|
|
4236
|
+
"",
|
|
4237
|
+
"Before any agent touches code, run:",
|
|
4238
|
+
"",
|
|
4239
|
+
"```",
|
|
4240
|
+
'runtrim go "<describe the task>"',
|
|
4241
|
+
"```",
|
|
4242
|
+
"",
|
|
4243
|
+
"This creates a scoped contract, loads project memory, and generates the guarded prompt for your agent.",
|
|
4244
|
+
"",
|
|
4245
|
+
"## Using your agent",
|
|
4246
|
+
"",
|
|
4247
|
+
"Paste the guarded prompt into Claude Code, Codex, Cursor, or any other AI coding agent.",
|
|
4248
|
+
"The agent receives allowed scope, forbidden areas, stop rules, and verification requirements.",
|
|
4249
|
+
"",
|
|
4250
|
+
"## After edits",
|
|
4251
|
+
"",
|
|
4252
|
+
"Run:",
|
|
4253
|
+
"",
|
|
4254
|
+
"```",
|
|
4255
|
+
"runtrim finish",
|
|
4256
|
+
"```",
|
|
4257
|
+
"",
|
|
4258
|
+
"This checks changed files, detects drift, scores risk, and saves the run report.",
|
|
4259
|
+
"",
|
|
4260
|
+
"## If you are an AI coding agent",
|
|
4261
|
+
"",
|
|
4262
|
+
"1. Read `.runtrim/contracts/latest.md` before touching any file.",
|
|
4263
|
+
"2. Stay inside the allowed scope defined in the contract.",
|
|
4264
|
+
"3. Do not touch forbidden systems or unrelated files.",
|
|
4265
|
+
"4. Stop immediately if scope must expand beyond the contract.",
|
|
4266
|
+
"5. Do not read, write, or reference `.env` files or secrets.",
|
|
4267
|
+
"6. Do not refactor code outside the direct task.",
|
|
4268
|
+
"7. After editing, tell the user to run: `runtrim finish`",
|
|
4269
|
+
"",
|
|
4270
|
+
"## Active contract",
|
|
4271
|
+
"",
|
|
4272
|
+
"If `.runtrim/contracts/latest.md` exists, it contains the active task contract.",
|
|
4273
|
+
"Follow it exactly.",
|
|
4274
|
+
"",
|
|
4275
|
+
"---",
|
|
4276
|
+
`Generated by RunTrim. Updated: ${now}`
|
|
4277
|
+
].join("\n");
|
|
4278
|
+
fs10.writeFileSync(runtrimMdPath, runtrimMd, "utf-8");
|
|
4279
|
+
const projectJsonPath = path10.join(configDir, "project.json");
|
|
4280
|
+
const projectJsonExists = fs10.existsSync(projectJsonPath);
|
|
4281
|
+
const projectJson = {
|
|
4282
|
+
name: baseline.projectName,
|
|
4283
|
+
stack: baseline.detectedStack,
|
|
4284
|
+
packageManager: baseline.packageManager,
|
|
4285
|
+
buildCommand: buildCmd,
|
|
4286
|
+
testCommand: testCmd,
|
|
4287
|
+
detectedAt: now
|
|
4288
|
+
};
|
|
4289
|
+
fs10.writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2), "utf-8");
|
|
4290
|
+
const policiesPath = path10.join(configDir, "policies.json");
|
|
4291
|
+
const policiesJsonExists = fs10.existsSync(policiesPath);
|
|
4292
|
+
const detectedSensitive = ((_g = baseline.riskSurfaces) != null ? _g : []).map((s) => s.type.toLowerCase());
|
|
4293
|
+
const policies = {
|
|
4294
|
+
version: 1,
|
|
4295
|
+
protected: [
|
|
4296
|
+
".env*",
|
|
4297
|
+
"secrets",
|
|
4298
|
+
"*.key",
|
|
4299
|
+
"*.pem",
|
|
4300
|
+
"auth/**",
|
|
4301
|
+
"middleware.ts",
|
|
4302
|
+
"prisma/schema.prisma",
|
|
4303
|
+
"prisma/migrations/**",
|
|
4304
|
+
"database/migrations/**",
|
|
4305
|
+
"stripe/**",
|
|
4306
|
+
"billing/**",
|
|
4307
|
+
"payment/**",
|
|
4308
|
+
"webhooks/**",
|
|
4309
|
+
"package-lock.json",
|
|
4310
|
+
"pnpm-lock.yaml",
|
|
4311
|
+
"yarn.lock",
|
|
4312
|
+
".next/**",
|
|
4313
|
+
"dist/**",
|
|
4314
|
+
"build/**",
|
|
4315
|
+
"node_modules/**"
|
|
4316
|
+
],
|
|
4317
|
+
sensitive: [
|
|
4318
|
+
"auth",
|
|
4319
|
+
"billing",
|
|
4320
|
+
"payment",
|
|
4321
|
+
"middleware",
|
|
4322
|
+
"database",
|
|
4323
|
+
"schema",
|
|
4324
|
+
"migrations",
|
|
4325
|
+
"env",
|
|
4326
|
+
"secrets",
|
|
4327
|
+
"webhooks",
|
|
4328
|
+
...detectedSensitive.filter((s) => !["auth", "billing", "payment", "middleware", "database", "env", "secrets", "webhooks"].includes(s))
|
|
4329
|
+
],
|
|
4330
|
+
note: "These areas require explicit task scope before any agent edits.",
|
|
4331
|
+
updatedAt: now
|
|
4332
|
+
};
|
|
4333
|
+
fs10.writeFileSync(policiesPath, JSON.stringify(policies, null, 2), "utf-8");
|
|
4334
|
+
const baselineMdPath = path10.join(configDir, "memory", "baseline.md");
|
|
4335
|
+
const baselineMdExists = fs10.existsSync(baselineMdPath);
|
|
4336
|
+
const protectedList = policies.protected.slice(0, 10).map((p) => `- ${p}`).join("\n");
|
|
4337
|
+
const stackLine = baseline.detectedStack.join(", ") || "unknown";
|
|
4338
|
+
const baselineMd = [
|
|
4339
|
+
"# RunTrim Memory \u2014 Baseline",
|
|
4340
|
+
"",
|
|
4341
|
+
`Project: ${baseline.projectName}`,
|
|
4342
|
+
`Stack: ${stackLine}`,
|
|
4343
|
+
`Package manager: ${baseline.packageManager}`,
|
|
4344
|
+
...buildCmd ? [`Build: ${buildCmd}`] : [],
|
|
4345
|
+
...testCmd ? [`Test: ${testCmd}`] : [],
|
|
4346
|
+
"",
|
|
4347
|
+
"## Protected areas",
|
|
4348
|
+
"",
|
|
4349
|
+
protectedList,
|
|
4350
|
+
"",
|
|
4351
|
+
"## Project rules",
|
|
4352
|
+
"",
|
|
4353
|
+
'- Start every AI task with: runtrim go "<task>"',
|
|
4354
|
+
"- Stay inside the scoped contract.",
|
|
4355
|
+
"- Run runtrim finish after agent edits.",
|
|
4356
|
+
"- No unrelated refactors during a task.",
|
|
4357
|
+
"- Never touch .env files.",
|
|
4358
|
+
"",
|
|
4359
|
+
"## Prior agent decisions",
|
|
4360
|
+
"",
|
|
4361
|
+
"No prior runs recorded. This is the baseline for this project.",
|
|
4362
|
+
"",
|
|
4363
|
+
"---",
|
|
4364
|
+
`Created by runtrim init. Updated: ${now}`
|
|
4365
|
+
].join("\n");
|
|
4366
|
+
fs10.writeFileSync(baselineMdPath, baselineMd, "utf-8");
|
|
4367
|
+
const agentResults = [];
|
|
4368
|
+
const agentTargets = ["CLAUDE.md", "AGENTS.md"];
|
|
4369
|
+
for (const filename of agentTargets) {
|
|
4370
|
+
const filePath = path10.join(cwd, filename);
|
|
4371
|
+
if (fs10.existsSync(filePath)) {
|
|
4372
|
+
const result = upsertProtocolBlock(filePath);
|
|
4373
|
+
if (result !== "skipped") agentResults.push({ file: filename, result });
|
|
4374
|
+
} else if (opts.agentFiles) {
|
|
4375
|
+
createMinimalAgentPointerFile(filePath, filename);
|
|
4376
|
+
agentResults.push({ file: filename, result: "created" });
|
|
4377
|
+
} else {
|
|
4378
|
+
agentResults.push({ file: filename, result: "skipped" });
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
let cursorResult = "skipped";
|
|
4382
|
+
const cursorDir = path10.join(cwd, ".cursor");
|
|
4383
|
+
const cursorExists = fs10.existsSync(cursorDir);
|
|
4384
|
+
if (opts.cursor || cursorExists) {
|
|
4385
|
+
const rulesDir = path10.join(cursorDir, "rules");
|
|
4386
|
+
const mdcPath = path10.join(rulesDir, "runtrim.mdc");
|
|
4387
|
+
if (!fs10.existsSync(rulesDir)) fs10.mkdirSync(rulesDir, { recursive: true });
|
|
4388
|
+
const existed = fs10.existsSync(mdcPath);
|
|
4389
|
+
const cursorMdc = [
|
|
4390
|
+
"---",
|
|
4391
|
+
"description: RunTrim guarded AI coding protocol",
|
|
4392
|
+
"alwaysApply: true",
|
|
4393
|
+
"---",
|
|
4394
|
+
"",
|
|
4395
|
+
"# RunTrim Protocol",
|
|
4396
|
+
"",
|
|
4397
|
+
"This repo uses RunTrim as the guarded AI coding control layer.",
|
|
4398
|
+
"",
|
|
4399
|
+
"## Before editing any code",
|
|
4400
|
+
"",
|
|
4401
|
+
"1. Read `RUNTRIM.md` in the repo root.",
|
|
4402
|
+
"2. If `.runtrim/contracts/latest.md` exists, read the active contract.",
|
|
4403
|
+
"",
|
|
4404
|
+
"## Rules",
|
|
4405
|
+
"",
|
|
4406
|
+
"- Stay inside the allowed scope defined in the contract.",
|
|
4407
|
+
"- Do not touch forbidden files or unrelated systems.",
|
|
4408
|
+
"- Stop immediately if scope must expand.",
|
|
4409
|
+
"- Do not read or write `.env` files.",
|
|
4410
|
+
"- Do not refactor outside the task scope.",
|
|
4411
|
+
"",
|
|
4412
|
+
"## After editing",
|
|
4413
|
+
"",
|
|
4414
|
+
"Tell the user to run: `runtrim finish`"
|
|
4415
|
+
].join("\n");
|
|
4416
|
+
fs10.writeFileSync(mdcPath, cursorMdc, "utf-8");
|
|
4417
|
+
cursorResult = existed ? "updated" : "created";
|
|
4418
|
+
}
|
|
4419
|
+
return {
|
|
4420
|
+
runtrimMd: runtrimMdExists ? "updated" : "created",
|
|
4421
|
+
projectJson: projectJsonExists ? "updated" : "created",
|
|
4422
|
+
policiesJson: policiesJsonExists ? "updated" : "created",
|
|
4423
|
+
baselineMd: baselineMdExists ? "updated" : "created",
|
|
4424
|
+
folders: createdFolders,
|
|
4425
|
+
agentFiles: agentResults,
|
|
4426
|
+
cursorRules: cursorResult
|
|
4427
|
+
};
|
|
4428
|
+
}
|
|
4429
|
+
program.command("init").description("Install the RunTrim protocol in the current project").option("--refresh", "Refresh baseline audit, rules, and memory without overwriting config").option("--agent-files", "Create CLAUDE.md and AGENTS.md if missing, with RunTrim pointer").option("--cursor", "Create .cursor/rules/runtrim.mdc Cursor agent instructions").action(async (options) => {
|
|
3909
4430
|
var _a2;
|
|
3910
4431
|
const cwd = process.cwd();
|
|
3911
4432
|
const allowed = await ensureRepoAllowedForFree(cwd);
|
|
3912
4433
|
if (!allowed) return;
|
|
3913
4434
|
console.log("");
|
|
3914
|
-
console.log(
|
|
4435
|
+
console.log(GO_ACCENT.bold("RunTrim init"));
|
|
3915
4436
|
console.log("");
|
|
3916
4437
|
const initResult = await initializeRunTrim(cwd, {
|
|
3917
4438
|
refresh: options.refresh,
|
|
@@ -3919,33 +4440,46 @@ program.command("init").description("Initialize RunTrim in the current project")
|
|
|
3919
4440
|
});
|
|
3920
4441
|
if (!initResult.ok) return;
|
|
3921
4442
|
const baseline = (_a2 = loadProjectAudit(cwd)) != null ? _a2 : performBaselineProjectAudit(cwd, null);
|
|
3922
|
-
const
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
console.log(DIM("
|
|
3928
|
-
console.log(
|
|
3929
|
-
console.log(DIM("
|
|
3930
|
-
console.log("");
|
|
3931
|
-
console.log(DIM(" Scripts found"));
|
|
3932
|
-
console.log(DIM(" ") + chalk.white(scriptNames.length ? scriptNames.join(", ") : "none"));
|
|
4443
|
+
const protocol = installProtocol(cwd, baseline, {
|
|
4444
|
+
agentFiles: options.agentFiles,
|
|
4445
|
+
cursor: options.cursor
|
|
4446
|
+
});
|
|
4447
|
+
const stackLine = baseline.detectedStack.length ? baseline.detectedStack.join(" + ") : "unknown stack";
|
|
4448
|
+
console.log(DIM(" Project"));
|
|
4449
|
+
console.log(chalk.white(" " + baseline.projectName));
|
|
4450
|
+
console.log(DIM(" " + stackLine));
|
|
3933
4451
|
console.log("");
|
|
3934
|
-
console.log(DIM("
|
|
3935
|
-
|
|
3936
|
-
|
|
4452
|
+
console.log(DIM(" Protocol"));
|
|
4453
|
+
const protocolFiles = [
|
|
4454
|
+
["RUNTRIM.md", protocol.runtrimMd],
|
|
4455
|
+
[".runtrim/project.json", protocol.projectJson],
|
|
4456
|
+
[".runtrim/policies.json", protocol.policiesJson],
|
|
4457
|
+
[".runtrim/memory/baseline.md", protocol.baselineMd]
|
|
4458
|
+
];
|
|
4459
|
+
for (const [file, result] of protocolFiles) {
|
|
4460
|
+
console.log(DIM(" ") + chalk.white(file.padEnd(34)) + DIM(result));
|
|
3937
4461
|
}
|
|
3938
4462
|
console.log("");
|
|
3939
|
-
console.log(DIM(
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
4463
|
+
console.log(DIM(" Agent pointers"));
|
|
4464
|
+
for (const { file, result } of protocol.agentFiles) {
|
|
4465
|
+
const color = result === "skipped" ? DIM : chalk.white;
|
|
4466
|
+
console.log(DIM(" ") + color(file.padEnd(34)) + DIM(result));
|
|
4467
|
+
}
|
|
4468
|
+
if (protocol.cursorRules !== "skipped") {
|
|
4469
|
+
console.log(DIM(" ") + chalk.white(".cursor/rules/runtrim.mdc".padEnd(34)) + DIM(protocol.cursorRules));
|
|
4470
|
+
} else if (options.cursor) {
|
|
4471
|
+
console.log(DIM(" Cursor rules skipped (.cursor/ not found and --cursor not passed)"));
|
|
4472
|
+
}
|
|
3946
4473
|
console.log("");
|
|
4474
|
+
if (protocol.folders.length > 0) {
|
|
4475
|
+
console.log(DIM(" Folders created"));
|
|
4476
|
+
for (const f of protocol.folders) {
|
|
4477
|
+
console.log(DIM(" " + f + "/"));
|
|
4478
|
+
}
|
|
4479
|
+
console.log("");
|
|
4480
|
+
}
|
|
3947
4481
|
console.log(DIM(" Next"));
|
|
3948
|
-
console.log(chalk.white(' runtrim
|
|
4482
|
+
console.log(chalk.white(' runtrim go "your first task"'));
|
|
3949
4483
|
console.log("");
|
|
3950
4484
|
});
|
|
3951
4485
|
var agentCommand = program.command("agent").description("Show or configure local agent execution settings");
|
|
@@ -4538,8 +5072,8 @@ program.command("run <task>").description("Guard then run configured local agent
|
|
|
4538
5072
|
console.log("");
|
|
4539
5073
|
return;
|
|
4540
5074
|
}
|
|
4541
|
-
const outputPath =
|
|
4542
|
-
|
|
5075
|
+
const outputPath = path10.join(getRunsDir(cwd), `${run.id}.output.txt`);
|
|
5076
|
+
fs10.writeFileSync(outputPath, `# stdout
|
|
4543
5077
|
${stdout}
|
|
4544
5078
|
|
|
4545
5079
|
# stderr
|
|
@@ -4616,7 +5150,8 @@ program.command("prepare <task>").description("Prepare a guarded prompt without
|
|
|
4616
5150
|
});
|
|
4617
5151
|
}
|
|
4618
5152
|
);
|
|
4619
|
-
program.command("go <task>").description("
|
|
5153
|
+
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) => {
|
|
5154
|
+
var _a2, _b, _c, _d, _e, _f, _g;
|
|
4620
5155
|
const cwd = process.cwd();
|
|
4621
5156
|
const allowed = await ensureRepoAllowedForFree(cwd);
|
|
4622
5157
|
if (!allowed) return;
|
|
@@ -4624,45 +5159,180 @@ program.command("go <task>").description("Daily shortcut: initialize if needed,
|
|
|
4624
5159
|
const initResult = await initializeRunTrim(cwd, { allowOverwritePrompt: false });
|
|
4625
5160
|
if (!initResult.ok) return;
|
|
4626
5161
|
}
|
|
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
5162
|
const config = loadConfig(cwd);
|
|
4639
|
-
const
|
|
4640
|
-
const
|
|
4641
|
-
const
|
|
4642
|
-
if (
|
|
4643
|
-
|
|
5163
|
+
const globalAuth = loadGlobalAuth();
|
|
5164
|
+
const rawToken = (_b = (_a2 = globalAuth == null ? void 0 : globalAuth.token) != null ? _a2 : config.syncToken) != null ? _b : null;
|
|
5165
|
+
const apiBase = resolveApiBase(config);
|
|
5166
|
+
if (rawToken == null ? void 0 : rawToken.startsWith("rt_live_")) {
|
|
5167
|
+
let serverResult = null;
|
|
5168
|
+
try {
|
|
5169
|
+
const res = await fetch(`${apiBase}/api/cli/usage/bridge-run`, {
|
|
5170
|
+
method: "POST",
|
|
5171
|
+
headers: { Authorization: `Bearer ${rawToken}` }
|
|
5172
|
+
});
|
|
5173
|
+
if (res.ok) {
|
|
5174
|
+
const body = await res.json();
|
|
5175
|
+
serverResult = {
|
|
5176
|
+
allowed: body.allowed,
|
|
5177
|
+
plan: body.plan,
|
|
5178
|
+
used: (_c = body.usage.bridgeRunsUsed) != null ? _c : 0,
|
|
5179
|
+
limit: body.usage.bridgeRunsLimit
|
|
5180
|
+
};
|
|
5181
|
+
}
|
|
5182
|
+
} catch (e) {
|
|
5183
|
+
}
|
|
5184
|
+
if (serverResult !== null && !serverResult.allowed) {
|
|
5185
|
+
printBridgeLimitMessage(serverResult.used);
|
|
5186
|
+
return;
|
|
5187
|
+
}
|
|
5188
|
+
} else {
|
|
5189
|
+
const check = checkAndIncrementLocalUsage();
|
|
5190
|
+
if (!check.allowed) {
|
|
5191
|
+
printBridgeLimitMessage(check.used);
|
|
5192
|
+
return;
|
|
5193
|
+
}
|
|
5194
|
+
}
|
|
5195
|
+
const auditSpinner = oraFactory({ text: " Auditing task...", color: "blue" }).start();
|
|
5196
|
+
await new Promise((r) => setTimeout(r, 180));
|
|
5197
|
+
const audit = auditTask(task, config, cwd);
|
|
5198
|
+
auditSpinner.stop();
|
|
5199
|
+
const contract = generateContract(task, audit, config);
|
|
5200
|
+
if (contract.isBlocked && contract.splitReport) {
|
|
5201
|
+
const sr = contract.splitReport;
|
|
5202
|
+
updateRun(saveRun(task, audit, contract, cwd).id, { status: "blocked" }, cwd);
|
|
5203
|
+
console.log("");
|
|
5204
|
+
console.log(GO_ACCENT.bold("RunTrim go"));
|
|
5205
|
+
console.log("");
|
|
5206
|
+
console.log(chalk.red.bold(" SPLIT REQUIRED"));
|
|
5207
|
+
console.log("");
|
|
5208
|
+
console.log(DIM(" Task ") + chalk.white(truncate(task, 60)));
|
|
5209
|
+
console.log(DIM(" Risk ") + chalk.red("CRITICAL"));
|
|
5210
|
+
console.log("");
|
|
5211
|
+
console.log(DIM(" This task crosses multiple high-risk systems."));
|
|
5212
|
+
console.log(DIM(" Running it in one agent session would cause scope drift and token waste."));
|
|
5213
|
+
console.log("");
|
|
5214
|
+
console.log(DIM(" Detected: ") + chalk.white(sr.detectedSystems.join(", ")));
|
|
5215
|
+
console.log("");
|
|
5216
|
+
for (const step of sr.recommendedSplit) {
|
|
5217
|
+
console.log(DIM(" ") + chalk.white(step));
|
|
5218
|
+
}
|
|
5219
|
+
console.log("");
|
|
5220
|
+
console.log(DIM(" Estimated waste avoided: ") + ACCENT(sr.estimatedWasteAvoided));
|
|
5221
|
+
console.log("");
|
|
5222
|
+
return;
|
|
5223
|
+
}
|
|
5224
|
+
const runs = loadAllRuns(cwd);
|
|
5225
|
+
const projectAudit = loadProjectAudit(cwd);
|
|
5226
|
+
const projectName = (_d = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _d : path10.basename(cwd);
|
|
5227
|
+
const memoryMarkdown = (() => {
|
|
5228
|
+
try {
|
|
5229
|
+
return readMemory(cwd);
|
|
5230
|
+
} catch (e) {
|
|
5231
|
+
return null;
|
|
5232
|
+
}
|
|
5233
|
+
})();
|
|
5234
|
+
const memoryUsed = Boolean(memoryMarkdown && memoryMarkdown.trim().length > 50);
|
|
5235
|
+
const run = saveRun(task, audit, contract, cwd);
|
|
5236
|
+
updateRun(run.id, {
|
|
5237
|
+
status: "guarded",
|
|
5238
|
+
bridgeMode: true,
|
|
5239
|
+
tokenBudget: deriveBridgeContext(task, contract, runs, projectName).tokenBudget,
|
|
5240
|
+
memoryUsed
|
|
5241
|
+
}, cwd);
|
|
5242
|
+
const bridgeCtx = deriveBridgeContext(task, contract, runs, projectName);
|
|
5243
|
+
let bridgeWritten = [];
|
|
5244
|
+
let bridgeManagedPaths = [];
|
|
5245
|
+
if (options.bridge !== false) {
|
|
5246
|
+
const result = writeBridgeFiles(bridgeCtx, cwd);
|
|
5247
|
+
bridgeWritten = result.written;
|
|
5248
|
+
bridgeManagedPaths = result.managedPaths;
|
|
5249
|
+
}
|
|
5250
|
+
const rawPrompt = contract.contractText;
|
|
5251
|
+
const fullPrompt = options.bridge !== false ? buildBridgePrompt(rawPrompt, bridgeCtx) : rawPrompt;
|
|
5252
|
+
const promptPath = writeLatestPromptFile(fullPrompt, config, cwd);
|
|
5253
|
+
const promptRelative = ".runtrim/latest-prompt.md";
|
|
5254
|
+
if (!bridgeManagedPaths.includes(promptRelative)) {
|
|
5255
|
+
bridgeManagedPaths.push(promptRelative);
|
|
5256
|
+
}
|
|
5257
|
+
const doCopy = options.clipboard !== false;
|
|
5258
|
+
const copied = doCopy ? await copyToClipboardSafe(fullPrompt) : false;
|
|
5259
|
+
if (options.monitor) void tryLaunchPanelMonitorDetached(cwd);
|
|
5260
|
+
updateRun(run.id, { bridgeManagedFiles: bridgeManagedPaths }, cwd);
|
|
5261
|
+
let synced = false;
|
|
5262
|
+
if (options.sync !== false) {
|
|
5263
|
+
const globalAuth2 = loadGlobalAuth();
|
|
5264
|
+
const rawToken2 = (_f = (_e = globalAuth2 == null ? void 0 : globalAuth2.token) != null ? _e : config.syncToken) != null ? _f : null;
|
|
5265
|
+
if (rawToken2 == null ? void 0 : rawToken2.startsWith("rt_live_")) {
|
|
5266
|
+
try {
|
|
5267
|
+
const freshRuns = loadAllRuns(cwd);
|
|
5268
|
+
const payload = buildSyncPayload({
|
|
5269
|
+
cwd,
|
|
5270
|
+
projectName,
|
|
5271
|
+
config,
|
|
5272
|
+
projectAudit: projectAudit != null ? projectAudit : null,
|
|
5273
|
+
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
5274
|
+
runs: freshRuns
|
|
5275
|
+
});
|
|
5276
|
+
const apiBase2 = resolveApiBase(config);
|
|
5277
|
+
const r = await fetch(`${apiBase2}/api/sync`, {
|
|
5278
|
+
method: "POST",
|
|
5279
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${rawToken2}` },
|
|
5280
|
+
body: JSON.stringify(payload)
|
|
5281
|
+
});
|
|
5282
|
+
synced = r.ok;
|
|
5283
|
+
} catch (e) {
|
|
5284
|
+
}
|
|
5285
|
+
}
|
|
4644
5286
|
}
|
|
5287
|
+
const riskColor = (_g = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[bridgeCtx.riskLevel]) != null ? _g : chalk.white;
|
|
4645
5288
|
console.log("");
|
|
4646
5289
|
console.log(GO_ACCENT.bold("RunTrim go"));
|
|
4647
5290
|
console.log("");
|
|
4648
5291
|
console.log(GO_ACCENT.bold("Task"));
|
|
4649
|
-
console.log(chalk.white(task));
|
|
5292
|
+
console.log(chalk.white(" " + task));
|
|
4650
5293
|
console.log("");
|
|
4651
|
-
console.log(GO_ACCENT.bold("
|
|
4652
|
-
console.log(
|
|
5294
|
+
console.log(GO_ACCENT.bold("Memory"));
|
|
5295
|
+
console.log(DIM(" " + (memoryUsed ? `Loaded ${runs.length} prior run${runs.length === 1 ? "" : "s"} and project context.` : "No prior runs. Starting from project context.")));
|
|
4653
5296
|
console.log("");
|
|
4654
|
-
console.log(GO_ACCENT.bold("
|
|
4655
|
-
console.log(
|
|
4656
|
-
console.log(chalk.white("
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
5297
|
+
console.log(GO_ACCENT.bold("Contract"));
|
|
5298
|
+
console.log(DIM(" Risk ") + riskColor(bridgeCtx.riskLevel));
|
|
5299
|
+
console.log(DIM(" Token budget ") + chalk.white("~" + bridgeCtx.tokenBudget.toLocaleString()));
|
|
5300
|
+
if (bridgeCtx.allowedScope.length > 0) {
|
|
5301
|
+
console.log(DIM(" Allowed ") + chalk.white(truncate(bridgeCtx.allowedScope.slice(0, 2).join(", "), 60)));
|
|
5302
|
+
}
|
|
5303
|
+
if (bridgeCtx.forbiddenScope.length > 0) {
|
|
5304
|
+
console.log(DIM(" Forbidden ") + chalk.white(truncate(bridgeCtx.forbiddenScope.slice(0, 2).join(", "), 60)));
|
|
5305
|
+
}
|
|
5306
|
+
console.log(DIM(" Run saved ") + chalk.white(`.runtrim/runs/${run.id}.json`));
|
|
4660
5307
|
console.log("");
|
|
4661
|
-
|
|
4662
|
-
|
|
5308
|
+
if (bridgeWritten.length > 0) {
|
|
5309
|
+
console.log(GO_ACCENT.bold("Bridge"));
|
|
5310
|
+
for (const f of bridgeWritten) {
|
|
5311
|
+
console.log(DIM(" ") + chalk.white(f));
|
|
5312
|
+
}
|
|
5313
|
+
console.log("");
|
|
5314
|
+
}
|
|
5315
|
+
if (options.sync !== false) {
|
|
5316
|
+
console.log(GO_ACCENT.bold("Cloud sync"));
|
|
5317
|
+
console.log(DIM(" ") + (synced ? chalk.white("Synced.") : DIM("Skipped. Run runtrim login to connect your dashboard.")));
|
|
5318
|
+
console.log("");
|
|
5319
|
+
}
|
|
5320
|
+
console.log(GO_ACCENT.bold("Prompt"));
|
|
5321
|
+
if (copied) {
|
|
5322
|
+
console.log(chalk.white(" Copied to clipboard."));
|
|
5323
|
+
} else if (!doCopy) {
|
|
5324
|
+
console.log(chalk.white(" Saved to: " + promptPath));
|
|
5325
|
+
} else {
|
|
5326
|
+
console.log(DIM(" Clipboard unavailable. Saved to: " + promptPath));
|
|
5327
|
+
}
|
|
5328
|
+
if (options.print) {
|
|
5329
|
+
console.log("");
|
|
5330
|
+
console.log(fullPrompt);
|
|
5331
|
+
}
|
|
4663
5332
|
console.log("");
|
|
4664
|
-
console.log(GO_ACCENT.bold("
|
|
4665
|
-
console.log(chalk.white("
|
|
5333
|
+
console.log(GO_ACCENT.bold("Next"));
|
|
5334
|
+
console.log(chalk.white(" Paste the guarded prompt into Claude Code, Codex, Cursor, or your agent."));
|
|
5335
|
+
console.log(chalk.white(" After edits are done, run: runtrim finish"));
|
|
4666
5336
|
console.log("");
|
|
4667
5337
|
});
|
|
4668
5338
|
program.command("check").description("Check the latest run and evaluate agent output").option("--json", "Print machine-readable check summary").action(async (options) => {
|
|
@@ -4828,80 +5498,6 @@ program.command("check").description("Check the latest run and evaluate agent ou
|
|
|
4828
5498
|
console.log(chalk.white(checkSummary.nextSafeAction));
|
|
4829
5499
|
console.log("");
|
|
4830
5500
|
});
|
|
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
5501
|
program.command("audit").description("Run baseline project audit and refresh local baseline files").action(() => {
|
|
4906
5502
|
const cwd = process.cwd();
|
|
4907
5503
|
if (!configExists(cwd)) {
|
|
@@ -4925,7 +5521,7 @@ program.command("audit").description("Run baseline project audit and refresh loc
|
|
|
4925
5521
|
const latest = loadLatestRun(cwd);
|
|
4926
5522
|
if (latest) writeMemoryFromRuns(latest, runs, nextConfig, cwd);
|
|
4927
5523
|
} else {
|
|
4928
|
-
|
|
5524
|
+
fs10.writeFileSync(path10.join(getConfigDir(cwd), "memory.md"), buildBaselineMemoryMarkdown(baseline), "utf-8");
|
|
4929
5525
|
}
|
|
4930
5526
|
console.log("");
|
|
4931
5527
|
console.log(BOLD("RunTrim") + DIM(" audit"));
|
|
@@ -5057,15 +5653,15 @@ program.command("memory").description("Show project memory and latest next safe
|
|
|
5057
5653
|
console.log("");
|
|
5058
5654
|
const config = configExists(cwd) ? loadConfig(cwd) : DEFAULT_CONFIG;
|
|
5059
5655
|
const audit = (_a2 = loadProjectAudit(cwd)) != null ? _a2 : performBaselineProjectAudit(cwd, null);
|
|
5060
|
-
const memoryPath =
|
|
5656
|
+
const memoryPath = path10.join(getConfigDir(cwd), "memory.md");
|
|
5061
5657
|
const latestRun = loadLatestRun(cwd);
|
|
5062
5658
|
if (!latestRun) {
|
|
5063
5659
|
let memory2 = readMemory(cwd);
|
|
5064
5660
|
if (!memory2) {
|
|
5065
|
-
const memoryDir =
|
|
5066
|
-
if (!
|
|
5661
|
+
const memoryDir = path10.dirname(memoryPath);
|
|
5662
|
+
if (!fs10.existsSync(memoryDir)) fs10.mkdirSync(memoryDir, { recursive: true });
|
|
5067
5663
|
memory2 = buildBaselineMemoryMarkdown(audit);
|
|
5068
|
-
|
|
5664
|
+
fs10.writeFileSync(memoryPath, memory2, "utf-8");
|
|
5069
5665
|
}
|
|
5070
5666
|
const baselinePrompt = 'runtrim go "your task"';
|
|
5071
5667
|
if (options.prompt) {
|
|
@@ -5206,13 +5802,13 @@ program.command("continue").description("Create a safe continuation prompt from
|
|
|
5206
5802
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
5207
5803
|
const continuationPath = resolveContinuationPath(cwd);
|
|
5208
5804
|
const latestPromptPath = resolvePromptPath(config, cwd);
|
|
5209
|
-
const latestPrompt =
|
|
5805
|
+
const latestPrompt = fs10.existsSync(latestPromptPath) ? fs10.readFileSync(latestPromptPath, "utf-8").trim() : "";
|
|
5210
5806
|
let memory = readMemory(cwd);
|
|
5211
5807
|
if (!memory) {
|
|
5212
5808
|
if (latestRun) memory = writeMemoryFromRuns(latestRun, allRuns, config, cwd);
|
|
5213
5809
|
else memory = buildBaselineMemoryMarkdown(audit);
|
|
5214
5810
|
}
|
|
5215
|
-
const projectName = audit.projectName ||
|
|
5811
|
+
const projectName = audit.projectName || path10.basename(cwd);
|
|
5216
5812
|
const stackLine = audit.detectedStack.length > 0 ? audit.detectedStack.join(", ") : "unknown";
|
|
5217
5813
|
const diffFiles = dedupeFiles(await getGitDiff(cwd));
|
|
5218
5814
|
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 +5984,13 @@ program.command("continue").description("Create a safe continuation prompt from
|
|
|
5388
5984
|
promptLines.push("");
|
|
5389
5985
|
}
|
|
5390
5986
|
const continuationPrompt = promptLines.join("\n");
|
|
5391
|
-
const continuationDir =
|
|
5392
|
-
if (!
|
|
5393
|
-
|
|
5987
|
+
const continuationDir = path10.dirname(continuationPath);
|
|
5988
|
+
if (!fs10.existsSync(continuationDir)) fs10.mkdirSync(continuationDir, { recursive: true });
|
|
5989
|
+
fs10.writeFileSync(continuationPath, continuationPrompt, "utf-8");
|
|
5394
5990
|
const copied = await copyToClipboardSafe(continuationPrompt);
|
|
5395
5991
|
if (memory) {
|
|
5396
5992
|
const memoryWithContinuation = updateMemoryWithContinuation(memory, reason, continuationPath, nowIso);
|
|
5397
|
-
|
|
5993
|
+
fs10.writeFileSync(path10.join(getConfigDir(cwd), "memory.md"), memoryWithContinuation, "utf-8");
|
|
5398
5994
|
}
|
|
5399
5995
|
if (hasConfig) {
|
|
5400
5996
|
const nextConfig = __spreadProps(__spreadValues({}, config), {
|
|
@@ -5505,7 +6101,7 @@ program.command("report").description("Show a summary of all local RunTrim runs"
|
|
|
5505
6101
|
console.log(DIM(" " + SECTION));
|
|
5506
6102
|
console.log("");
|
|
5507
6103
|
const audit = loadProjectAudit(cwd);
|
|
5508
|
-
console.log(DIM(" Project ") + chalk.white((_b = audit == null ? void 0 : audit.projectName) != null ? _b :
|
|
6104
|
+
console.log(DIM(" Project ") + chalk.white((_b = audit == null ? void 0 : audit.projectName) != null ? _b : path10.basename(cwd)));
|
|
5509
6105
|
console.log(DIM(" Focus ") + chalk.white(truncate((_d = (_c = latestRun.contract.contract) == null ? void 0 : _c.cleanedObjective) != null ? _d : latestRun.task, 58)));
|
|
5510
6106
|
console.log(DIM(" Status ") + chalk.white(formatStatus((_e = latestEval == null ? void 0 : latestEval.status) != null ? _e : latestRun.status)));
|
|
5511
6107
|
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 +6164,432 @@ var statusColors = {
|
|
|
5568
6164
|
drift_detected: chalk.red,
|
|
5569
6165
|
blocked: chalk.red
|
|
5570
6166
|
};
|
|
6167
|
+
var GLOBAL_USAGE_FILE = path10.join(os3.homedir(), ".runtrim", "usage.json");
|
|
6168
|
+
var FREE_BRIDGE_LIMIT_LOCAL = 5;
|
|
6169
|
+
function currentUsagePeriod() {
|
|
6170
|
+
const d = /* @__PURE__ */ new Date();
|
|
6171
|
+
return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`;
|
|
6172
|
+
}
|
|
6173
|
+
function loadLocalUsageRuns() {
|
|
6174
|
+
var _a2;
|
|
6175
|
+
try {
|
|
6176
|
+
const raw = JSON.parse(fs10.readFileSync(GLOBAL_USAGE_FILE, "utf-8"));
|
|
6177
|
+
return (_a2 = raw.bridgeRuns) != null ? _a2 : {};
|
|
6178
|
+
} catch (e) {
|
|
6179
|
+
return {};
|
|
6180
|
+
}
|
|
6181
|
+
}
|
|
6182
|
+
function saveLocalUsageRuns(bridgeRuns) {
|
|
6183
|
+
const dir = path10.dirname(GLOBAL_USAGE_FILE);
|
|
6184
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
6185
|
+
fs10.writeFileSync(GLOBAL_USAGE_FILE, JSON.stringify({ bridgeRuns }, null, 2), "utf-8");
|
|
6186
|
+
}
|
|
6187
|
+
function checkAndIncrementLocalUsage() {
|
|
6188
|
+
var _a2;
|
|
6189
|
+
const period = currentUsagePeriod();
|
|
6190
|
+
const runs = loadLocalUsageRuns();
|
|
6191
|
+
const used = (_a2 = runs[period]) != null ? _a2 : 0;
|
|
6192
|
+
if (used >= FREE_BRIDGE_LIMIT_LOCAL) {
|
|
6193
|
+
return { allowed: false, used };
|
|
6194
|
+
}
|
|
6195
|
+
runs[period] = used + 1;
|
|
6196
|
+
saveLocalUsageRuns(runs);
|
|
6197
|
+
return { allowed: true, used: used + 1 };
|
|
6198
|
+
}
|
|
6199
|
+
function printBridgeLimitMessage(used) {
|
|
6200
|
+
console.log("");
|
|
6201
|
+
console.log(chalk.red.bold(" Free Bridge limit reached."));
|
|
6202
|
+
console.log("");
|
|
6203
|
+
console.log(chalk.white(` You have used ${used} local guarded run${used === 1 ? "" : "s"} this month.`));
|
|
6204
|
+
console.log(DIM(" Upgrade to Pro for unlimited Bridge Mode, cloud sync, project memory,"));
|
|
6205
|
+
console.log(DIM(" reports, and continuation history."));
|
|
6206
|
+
console.log("");
|
|
6207
|
+
console.log(chalk.white(" https://www.runtrim.com/pricing"));
|
|
6208
|
+
console.log("");
|
|
6209
|
+
}
|
|
6210
|
+
var GLOBAL_AUTH_DIR = path10.join(os3.homedir(), ".runtrim");
|
|
6211
|
+
var GLOBAL_AUTH_FILE = path10.join(GLOBAL_AUTH_DIR, "auth.json");
|
|
6212
|
+
function loadGlobalAuth() {
|
|
6213
|
+
if (!fs10.existsSync(GLOBAL_AUTH_FILE)) return null;
|
|
6214
|
+
try {
|
|
6215
|
+
return JSON.parse(fs10.readFileSync(GLOBAL_AUTH_FILE, "utf-8"));
|
|
6216
|
+
} catch (e) {
|
|
6217
|
+
return null;
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
function saveGlobalAuth(auth) {
|
|
6221
|
+
if (!fs10.existsSync(GLOBAL_AUTH_DIR)) {
|
|
6222
|
+
fs10.mkdirSync(GLOBAL_AUTH_DIR, { recursive: true });
|
|
6223
|
+
}
|
|
6224
|
+
fs10.writeFileSync(GLOBAL_AUTH_FILE, JSON.stringify(auth, null, 2));
|
|
6225
|
+
}
|
|
6226
|
+
function resolveApiBase(config) {
|
|
6227
|
+
var _a2;
|
|
6228
|
+
const url = (_a2 = config.dashboardUrl) == null ? void 0 : _a2.trim();
|
|
6229
|
+
if (!url || url.startsWith("http://localhost")) {
|
|
6230
|
+
return "https://www.runtrim.com";
|
|
6231
|
+
}
|
|
6232
|
+
try {
|
|
6233
|
+
return new URL(url).origin;
|
|
6234
|
+
} catch (e) {
|
|
6235
|
+
return "https://www.runtrim.com";
|
|
6236
|
+
}
|
|
6237
|
+
}
|
|
6238
|
+
program.command("login").description("Connect this machine to your RunTrim cloud account").option("--token <token>", "CLI token (skip interactive prompt)").action(async (opts) => {
|
|
6239
|
+
var _a2, _b, _c, _d, _e;
|
|
6240
|
+
console.log("");
|
|
6241
|
+
console.log(BOLD("RunTrim") + DIM(" connect to cloud"));
|
|
6242
|
+
console.log("");
|
|
6243
|
+
const cwd = process.cwd();
|
|
6244
|
+
const existing = loadGlobalAuth();
|
|
6245
|
+
if (existing && !opts.token) {
|
|
6246
|
+
console.log(DIM(" Already connected."));
|
|
6247
|
+
if (existing.email) {
|
|
6248
|
+
console.log(DIM(" Account ") + chalk.white(existing.email));
|
|
6249
|
+
}
|
|
6250
|
+
console.log(DIM(" Token ") + chalk.white("rt_live_..."));
|
|
6251
|
+
console.log("");
|
|
6252
|
+
console.log(DIM(" To reconnect, run: ") + GO_ACCENT("runtrim login --token <new-token>"));
|
|
6253
|
+
console.log("");
|
|
6254
|
+
return;
|
|
6255
|
+
}
|
|
6256
|
+
console.log(DIM(" Get your CLI token from:"));
|
|
6257
|
+
console.log(" " + GO_ACCENT("https://www.runtrim.com/app/connect"));
|
|
6258
|
+
console.log("");
|
|
6259
|
+
let rawToken = (_b = (_a2 = opts.token) == null ? void 0 : _a2.trim()) != null ? _b : "";
|
|
6260
|
+
if (!rawToken) {
|
|
6261
|
+
const answer = await prompts({
|
|
6262
|
+
type: "text",
|
|
6263
|
+
name: "token",
|
|
6264
|
+
message: "Paste your CLI token"
|
|
6265
|
+
});
|
|
6266
|
+
rawToken = (_d = (_c = answer.token) == null ? void 0 : _c.trim()) != null ? _d : "";
|
|
6267
|
+
}
|
|
6268
|
+
if (!rawToken || !rawToken.startsWith("rt_live_")) {
|
|
6269
|
+
console.log(chalk.yellow(" Invalid token. Tokens start with rt_live_"));
|
|
6270
|
+
console.log("");
|
|
6271
|
+
return;
|
|
6272
|
+
}
|
|
6273
|
+
const config = configExists(cwd) ? loadConfig(cwd) : DEFAULT_CONFIG;
|
|
6274
|
+
const apiBase = resolveApiBase(config);
|
|
6275
|
+
const verifyUrl = `${apiBase}/api/cli-token/verify`;
|
|
6276
|
+
const spinner = oraFactory({ text: " Verifying token...", color: "blue" }).start();
|
|
6277
|
+
let email;
|
|
6278
|
+
try {
|
|
6279
|
+
const res = await fetch(verifyUrl, {
|
|
6280
|
+
method: "GET",
|
|
6281
|
+
headers: { Authorization: `Bearer ${rawToken}` }
|
|
6282
|
+
});
|
|
6283
|
+
const body = await res.json();
|
|
6284
|
+
if (!res.ok || !body.ok) {
|
|
6285
|
+
spinner.fail(" Invalid token. " + ((_e = body.error) != null ? _e : ""));
|
|
6286
|
+
console.log("");
|
|
6287
|
+
return;
|
|
6288
|
+
}
|
|
6289
|
+
email = body.email;
|
|
6290
|
+
spinner.succeed(" Token verified.");
|
|
6291
|
+
} catch (e) {
|
|
6292
|
+
spinner.fail(" Could not reach RunTrim server. Check your connection.");
|
|
6293
|
+
console.log("");
|
|
6294
|
+
return;
|
|
6295
|
+
}
|
|
6296
|
+
saveGlobalAuth({ token: rawToken, storedAt: (/* @__PURE__ */ new Date()).toISOString(), email });
|
|
6297
|
+
console.log("");
|
|
6298
|
+
if (email) {
|
|
6299
|
+
console.log(ACCENT.bold(" Connected as ") + chalk.white(email));
|
|
6300
|
+
} else {
|
|
6301
|
+
console.log(ACCENT.bold(" Connected to RunTrim cloud."));
|
|
6302
|
+
}
|
|
6303
|
+
console.log(DIM(" Token stored in ") + chalk.white("~/.runtrim/auth.json"));
|
|
6304
|
+
console.log("");
|
|
6305
|
+
console.log(DIM(" Next: navigate to a project and run ") + GO_ACCENT("runtrim sync"));
|
|
6306
|
+
console.log("");
|
|
6307
|
+
});
|
|
6308
|
+
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) => {
|
|
6309
|
+
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
|
|
6310
|
+
const cwd = process.cwd();
|
|
6311
|
+
console.log("");
|
|
6312
|
+
console.log(GO_ACCENT.bold("RunTrim finish"));
|
|
6313
|
+
console.log("");
|
|
6314
|
+
const allRuns = loadAllRuns(cwd);
|
|
6315
|
+
const activeRun = allRuns.find((r) => r.status === "guarded" || r.status === "checked");
|
|
6316
|
+
if (!activeRun) {
|
|
6317
|
+
console.log(chalk.yellow(" No active RunTrim session found."));
|
|
6318
|
+
console.log(DIM(' Start a new session with: runtrim go "<task>"'));
|
|
6319
|
+
console.log("");
|
|
6320
|
+
return;
|
|
6321
|
+
}
|
|
6322
|
+
const config = configExists(cwd) ? loadConfig(cwd) : DEFAULT_CONFIG;
|
|
6323
|
+
const projectAudit = loadProjectAudit(cwd);
|
|
6324
|
+
const projectName = (_a2 = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _a2 : path10.basename(cwd);
|
|
6325
|
+
console.log(DIM(" Run ") + chalk.white(truncate(activeRun.task, 60)));
|
|
6326
|
+
console.log(DIM(" Run ID ") + chalk.white(activeRun.id));
|
|
6327
|
+
console.log("");
|
|
6328
|
+
const allChangedFiles = dedupeFiles(await getGitDiff(cwd));
|
|
6329
|
+
const sessionManagedFiles = (_b = activeRun.bridgeManagedFiles) != null ? _b : [];
|
|
6330
|
+
function isRuntrimOwned(f) {
|
|
6331
|
+
const norm = f.replace(/\\/g, "/").toLowerCase();
|
|
6332
|
+
if (norm.startsWith(".runtrim/")) return true;
|
|
6333
|
+
if (norm === "runtrim.md") return true;
|
|
6334
|
+
if (sessionManagedFiles.some(
|
|
6335
|
+
(m) => m.replace(/\\/g, "/").toLowerCase() === norm
|
|
6336
|
+
)) return true;
|
|
6337
|
+
return false;
|
|
6338
|
+
}
|
|
6339
|
+
const runtrimFiles = [];
|
|
6340
|
+
const agentFiles = [];
|
|
6341
|
+
for (const f of allChangedFiles) {
|
|
6342
|
+
if (isRuntrimOwned(f)) {
|
|
6343
|
+
runtrimFiles.push(f);
|
|
6344
|
+
} else {
|
|
6345
|
+
agentFiles.push(f);
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
const changedFiles = agentFiles;
|
|
6349
|
+
const maxFiles = inferMaxFilesFromScope(
|
|
6350
|
+
(_d = (_c = activeRun.contract.contract) == null ? void 0 : _c.relevantScope) != null ? _d : [],
|
|
6351
|
+
config.maxFilesPerRun
|
|
6352
|
+
);
|
|
6353
|
+
const scope = evaluateWatchState({
|
|
6354
|
+
changedFiles,
|
|
6355
|
+
run: activeRun,
|
|
6356
|
+
maxFilesPerRun: maxFiles,
|
|
6357
|
+
strict: false
|
|
6358
|
+
});
|
|
6359
|
+
const evaluation = evaluateAgentOutput(null, changedFiles, {
|
|
6360
|
+
task: activeRun.task,
|
|
6361
|
+
relevantScope: (_f = (_e = activeRun.contract.contract) == null ? void 0 : _e.relevantScope) != null ? _f : [],
|
|
6362
|
+
sensitiveScope: (_h = (_g = activeRun.contract.contract) == null ? void 0 : _g.sensitiveScope) != null ? _h : [],
|
|
6363
|
+
forbiddenScope: (_j = (_i = activeRun.contract.contract) == null ? void 0 : _i.forbiddenScope) != null ? _j : [],
|
|
6364
|
+
runStatus: activeRun.status
|
|
6365
|
+
});
|
|
6366
|
+
let scopeDriftStatus = "passed";
|
|
6367
|
+
if (scope.forbiddenFiles.length > 0) scopeDriftStatus = "forbidden_touched";
|
|
6368
|
+
else if (scope.outOfScopeFiles.length > 0) scopeDriftStatus = "out_of_scope";
|
|
6369
|
+
else if (evaluation.scopeDriftRisk === "high" || evaluation.scopeDriftRisk === "medium")
|
|
6370
|
+
scopeDriftStatus = "drift_detected";
|
|
6371
|
+
const forbiddenCount = scope.forbiddenFiles.length;
|
|
6372
|
+
const reportParts = [];
|
|
6373
|
+
if (changedFiles.length === 0) {
|
|
6374
|
+
reportParts.push("No agent changes detected.");
|
|
6375
|
+
} else {
|
|
6376
|
+
reportParts.push(`${changedFiles.length} file${changedFiles.length === 1 ? "" : "s"} changed.`);
|
|
6377
|
+
}
|
|
6378
|
+
if (forbiddenCount > 0) {
|
|
6379
|
+
reportParts.push(`${forbiddenCount} forbidden file${forbiddenCount === 1 ? "" : "s"} touched.`);
|
|
6380
|
+
} else if (changedFiles.length > 0) {
|
|
6381
|
+
reportParts.push("No forbidden systems touched.");
|
|
6382
|
+
}
|
|
6383
|
+
if (scopeDriftStatus === "passed" && changedFiles.length > 0) {
|
|
6384
|
+
reportParts.push("Changes within contract.");
|
|
6385
|
+
}
|
|
6386
|
+
if (evaluation.memorySummary) reportParts.push(evaluation.memorySummary);
|
|
6387
|
+
const reportSummary = reportParts.join(" ");
|
|
6388
|
+
const continuationPack = evaluation.nextGuardedPrompt || null;
|
|
6389
|
+
if (continuationPack) {
|
|
6390
|
+
const contDir = getConfigDir(cwd);
|
|
6391
|
+
if (!fs10.existsSync(contDir)) fs10.mkdirSync(contDir, { recursive: true });
|
|
6392
|
+
fs10.writeFileSync(path10.join(contDir, "continuation-prompt.md"), continuationPack, "utf-8");
|
|
6393
|
+
}
|
|
6394
|
+
const evalRecord = __spreadProps(__spreadValues({}, evaluation), {
|
|
6395
|
+
nextPrompt: evaluation.nextGuardedPrompt,
|
|
6396
|
+
nextSafePrompt: evaluation.nextGuardedPrompt,
|
|
6397
|
+
nextSafeAction: evaluation.nextSafeAction,
|
|
6398
|
+
memorySummary: evaluation.memorySummary,
|
|
6399
|
+
evaluatedAt: evaluation.evaluatedAt
|
|
6400
|
+
});
|
|
6401
|
+
updateRun(activeRun.id, {
|
|
6402
|
+
status: "completed",
|
|
6403
|
+
evaluation: evalRecord,
|
|
6404
|
+
scopeDriftStatus,
|
|
6405
|
+
reportSummary,
|
|
6406
|
+
watchStatus: scope.status,
|
|
6407
|
+
watchWarnings: scope.warnings,
|
|
6408
|
+
watchChangedFiles: agentFiles
|
|
6409
|
+
// only agent changes, not RunTrim protocol files
|
|
6410
|
+
}, cwd);
|
|
6411
|
+
const freshRuns = loadAllRuns(cwd);
|
|
6412
|
+
const updatedRun = (_k = freshRuns.find((r) => r.id === activeRun.id)) != null ? _k : activeRun;
|
|
6413
|
+
writeMemoryFromRuns(updatedRun, freshRuns, config, cwd);
|
|
6414
|
+
let synced = false;
|
|
6415
|
+
if (options.sync !== false) {
|
|
6416
|
+
const globalAuth = loadGlobalAuth();
|
|
6417
|
+
const rawToken = (_m = (_l = globalAuth == null ? void 0 : globalAuth.token) != null ? _l : config.syncToken) != null ? _m : null;
|
|
6418
|
+
if (rawToken == null ? void 0 : rawToken.startsWith("rt_live_")) {
|
|
6419
|
+
try {
|
|
6420
|
+
const memoryMarkdown = (() => {
|
|
6421
|
+
try {
|
|
6422
|
+
return readMemory(cwd);
|
|
6423
|
+
} catch (e) {
|
|
6424
|
+
return null;
|
|
6425
|
+
}
|
|
6426
|
+
})();
|
|
6427
|
+
const payload = buildSyncPayload({
|
|
6428
|
+
cwd,
|
|
6429
|
+
projectName,
|
|
6430
|
+
config,
|
|
6431
|
+
projectAudit: projectAudit != null ? projectAudit : null,
|
|
6432
|
+
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
6433
|
+
runs: freshRuns
|
|
6434
|
+
});
|
|
6435
|
+
const apiBase = resolveApiBase(config);
|
|
6436
|
+
const r = await fetch(`${apiBase}/api/sync`, {
|
|
6437
|
+
method: "POST",
|
|
6438
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${rawToken}` },
|
|
6439
|
+
body: JSON.stringify(payload)
|
|
6440
|
+
});
|
|
6441
|
+
synced = r.ok;
|
|
6442
|
+
} catch (e) {
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
const scopeColor = scopeDriftStatus === "passed" ? chalk.green : scopeDriftStatus === "forbidden_touched" ? chalk.red : chalk.yellow;
|
|
6447
|
+
const riskAfter = (_n = activeRun.contract.wasteRiskAfter) != null ? _n : "medium";
|
|
6448
|
+
const riskColor = (_o = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[riskAfter]) != null ? _o : chalk.white;
|
|
6449
|
+
console.log(GO_ACCENT.bold("Run"));
|
|
6450
|
+
console.log(chalk.white(" " + truncate(activeRun.task, 70)));
|
|
6451
|
+
console.log("");
|
|
6452
|
+
if (changedFiles.length > 0) {
|
|
6453
|
+
console.log(GO_ACCENT.bold("Changed files"));
|
|
6454
|
+
for (const f of changedFiles.slice(0, 8)) {
|
|
6455
|
+
const isForbidden = scope.forbiddenFiles.includes(f);
|
|
6456
|
+
const isSensitive = scope.sensitiveFiles.includes(f);
|
|
6457
|
+
const marker = isForbidden ? chalk.red(" [forbidden]") : isSensitive ? chalk.yellow(" [sensitive]") : "";
|
|
6458
|
+
console.log(chalk.white(" - " + f) + marker);
|
|
6459
|
+
}
|
|
6460
|
+
if (changedFiles.length > 8) {
|
|
6461
|
+
console.log(DIM(` ... and ${changedFiles.length - 8} more`));
|
|
6462
|
+
}
|
|
6463
|
+
console.log("");
|
|
6464
|
+
} else {
|
|
6465
|
+
console.log(GO_ACCENT.bold("Changed files"));
|
|
6466
|
+
console.log(DIM(" No agent changes detected."));
|
|
6467
|
+
console.log("");
|
|
6468
|
+
}
|
|
6469
|
+
if (runtrimFiles.length > 0) {
|
|
6470
|
+
console.log(GO_ACCENT.bold("RunTrim files"));
|
|
6471
|
+
for (const f of runtrimFiles) {
|
|
6472
|
+
console.log(DIM(" - " + f));
|
|
6473
|
+
}
|
|
6474
|
+
console.log("");
|
|
6475
|
+
}
|
|
6476
|
+
console.log(GO_ACCENT.bold("Scope"));
|
|
6477
|
+
if (changedFiles.length === 0) {
|
|
6478
|
+
console.log(chalk.green(" No agent changes to evaluate."));
|
|
6479
|
+
} else {
|
|
6480
|
+
console.log(scopeColor(" " + (scopeDriftStatus === "passed" ? "Passed" : scopeDriftStatus === "forbidden_touched" ? "Failed \u2014 forbidden files touched" : "Drift detected")));
|
|
6481
|
+
}
|
|
6482
|
+
console.log("");
|
|
6483
|
+
console.log(GO_ACCENT.bold("Risk"));
|
|
6484
|
+
console.log(riskColor(" " + riskAfter));
|
|
6485
|
+
console.log("");
|
|
6486
|
+
console.log(GO_ACCENT.bold("Report"));
|
|
6487
|
+
console.log(chalk.white(" " + reportSummary));
|
|
6488
|
+
console.log("");
|
|
6489
|
+
if (continuationPack) {
|
|
6490
|
+
console.log(GO_ACCENT.bold("Continuation"));
|
|
6491
|
+
console.log(chalk.white(" Saved to .runtrim/continuation-prompt.md"));
|
|
6492
|
+
console.log("");
|
|
6493
|
+
}
|
|
6494
|
+
if (evaluation.nextSafeAction && evaluation.nextSafeAction !== "Run is ready to continue.") {
|
|
6495
|
+
console.log(GO_ACCENT.bold("Next safest step"));
|
|
6496
|
+
console.log(chalk.white(" " + evaluation.nextSafeAction));
|
|
6497
|
+
console.log("");
|
|
6498
|
+
}
|
|
6499
|
+
if (options.sync !== false) {
|
|
6500
|
+
console.log(GO_ACCENT.bold("Cloud sync"));
|
|
6501
|
+
console.log(DIM(" ") + (synced ? chalk.white("Completed.") : DIM("Skipped. Run runtrim login to connect your dashboard.")));
|
|
6502
|
+
console.log("");
|
|
6503
|
+
}
|
|
6504
|
+
});
|
|
6505
|
+
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) => {
|
|
6506
|
+
var _a2, _b, _c, _d, _e, _f;
|
|
6507
|
+
const cwd = process.cwd();
|
|
6508
|
+
console.log("");
|
|
6509
|
+
console.log(BOLD("RunTrim") + DIM(" cloud sync"));
|
|
6510
|
+
console.log("");
|
|
6511
|
+
const globalAuth = loadGlobalAuth();
|
|
6512
|
+
const config = configExists(cwd) ? loadConfig(cwd) : DEFAULT_CONFIG;
|
|
6513
|
+
const rawToken = (_b = (_a2 = globalAuth == null ? void 0 : globalAuth.token) != null ? _a2 : config.syncToken) != null ? _b : null;
|
|
6514
|
+
if (!rawToken) {
|
|
6515
|
+
console.log(chalk.yellow(" No CLI token found."));
|
|
6516
|
+
console.log(DIM(" Run ") + GO_ACCENT("runtrim login") + DIM(" to connect cloud sync."));
|
|
6517
|
+
console.log(DIM(" Local CLI still works without a token."));
|
|
6518
|
+
console.log("");
|
|
6519
|
+
return;
|
|
6520
|
+
}
|
|
6521
|
+
if (!rawToken.startsWith("rt_live_")) {
|
|
6522
|
+
console.log(chalk.yellow(" Stored token format is invalid. Re-run: runtrim login"));
|
|
6523
|
+
console.log("");
|
|
6524
|
+
return;
|
|
6525
|
+
}
|
|
6526
|
+
const apiBase = resolveApiBase(config);
|
|
6527
|
+
const syncUrl = `${apiBase}/api/sync`;
|
|
6528
|
+
const runs = loadAllRuns(cwd);
|
|
6529
|
+
if (runs.length === 0) {
|
|
6530
|
+
console.log(DIM(" No local runs found in this directory."));
|
|
6531
|
+
console.log(DIM(" Run ") + GO_ACCENT('runtrim go "your task"') + DIM(" first to create runs."));
|
|
6532
|
+
console.log("");
|
|
6533
|
+
return;
|
|
6534
|
+
}
|
|
6535
|
+
const projectAudit = loadProjectAudit(cwd);
|
|
6536
|
+
const memoryMarkdown = (() => {
|
|
6537
|
+
try {
|
|
6538
|
+
return readMemory(cwd);
|
|
6539
|
+
} catch (e) {
|
|
6540
|
+
return "";
|
|
6541
|
+
}
|
|
6542
|
+
})();
|
|
6543
|
+
const payload = buildSyncPayload({
|
|
6544
|
+
cwd,
|
|
6545
|
+
projectName: (_c = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _c : path10.basename(cwd),
|
|
6546
|
+
config,
|
|
6547
|
+
projectAudit: projectAudit != null ? projectAudit : null,
|
|
6548
|
+
memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
|
|
6549
|
+
runs
|
|
6550
|
+
});
|
|
6551
|
+
console.log(DIM(" Project ") + chalk.white(payload.project.name));
|
|
6552
|
+
console.log(DIM(" Runs ") + chalk.white(String(payload.runs.length)));
|
|
6553
|
+
console.log(DIM(" API ") + chalk.white(syncUrl));
|
|
6554
|
+
console.log("");
|
|
6555
|
+
if (opts.dryRun) {
|
|
6556
|
+
console.log(ACCENT.bold(" Dry run \u2014 nothing uploaded."));
|
|
6557
|
+
console.log("");
|
|
6558
|
+
return;
|
|
6559
|
+
}
|
|
6560
|
+
const spinner = oraFactory({ text: " Syncing...", color: "blue" }).start();
|
|
6561
|
+
try {
|
|
6562
|
+
const res = await fetch(syncUrl, {
|
|
6563
|
+
method: "POST",
|
|
6564
|
+
headers: {
|
|
6565
|
+
"Content-Type": "application/json",
|
|
6566
|
+
Authorization: `Bearer ${rawToken}`
|
|
6567
|
+
},
|
|
6568
|
+
body: JSON.stringify(payload)
|
|
6569
|
+
});
|
|
6570
|
+
const body = await res.json();
|
|
6571
|
+
if (!res.ok || !body.ok) {
|
|
6572
|
+
spinner.fail(" Sync failed.");
|
|
6573
|
+
console.log("");
|
|
6574
|
+
if (body.error) {
|
|
6575
|
+
console.log(chalk.red(" Error: ") + chalk.white(body.error));
|
|
6576
|
+
if (res.status === 401) {
|
|
6577
|
+
console.log(DIM(" Token may be invalid or expired. Run: runtrim login"));
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6580
|
+
console.log("");
|
|
6581
|
+
return;
|
|
6582
|
+
}
|
|
6583
|
+
spinner.succeed(" Sync complete.");
|
|
6584
|
+
console.log("");
|
|
6585
|
+
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"}`));
|
|
6586
|
+
console.log(DIM(" Project ID ") + chalk.white((_f = body.projectId) != null ? _f : "\u2014"));
|
|
6587
|
+
console.log("");
|
|
6588
|
+
console.log(DIM(" View at ") + GO_ACCENT(`${apiBase}/app`));
|
|
6589
|
+
console.log("");
|
|
6590
|
+
} catch (err) {
|
|
6591
|
+
spinner.fail(" Network error. Check your connection.");
|
|
6592
|
+
console.log("");
|
|
6593
|
+
}
|
|
6594
|
+
});
|
|
5571
6595
|
program.parse(process.argv);
|