runtrim 0.1.9 → 0.1.11

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.
Files changed (2) hide show
  1. package/dist-cli/runtrim.js +292 -214
  2. package/package.json +1 -1
@@ -1947,15 +1947,6 @@ import fs7 from "fs";
1947
1947
  import path7 from "path";
1948
1948
  var BRIDGE_START = "<!-- RUNTRIM_BRIDGE_START -->";
1949
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
1950
  var TOKEN_BUDGET_MAP = {
1960
1951
  low: 1e4,
1961
1952
  medium: 25e3,
@@ -1967,10 +1958,9 @@ function deriveBridgeContext(task, contract, recentRuns, projectName) {
1967
1958
  const c = contract.contract;
1968
1959
  const riskLevel = (_a2 = contract.wasteRiskAfter) != null ? _a2 : "medium";
1969
1960
  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
- ];
1961
+ const stopFromRules = (_c = c.stopRules) != null ? _c : [];
1962
+ const stopFromAsk = ((_d = c.whenToAsk) != null ? _d : []).map((s) => `Stop and ask before: ${s}`);
1963
+ const stopConditions = [...stopFromRules, ...stopFromAsk];
1974
1964
  const verificationSteps = (_e = c.successCriteria) != null ? _e : [];
1975
1965
  const lines = recentRuns.slice(0, 3).map((r) => {
1976
1966
  const day = new Date(r.createdAt).toLocaleDateString("en-US", {
@@ -1994,6 +1984,126 @@ ${lines.join("\n")}` : "No prior runs. This is the first run for this project.";
1994
1984
  projectName
1995
1985
  };
1996
1986
  }
1987
+ function writeCanonicalRuntrimMd(cwd = process.cwd(), projectName) {
1988
+ const lines = [
1989
+ "# RunTrim Protocol",
1990
+ "",
1991
+ ...projectName ? [`Project: ${projectName}`, ""] : [],
1992
+ "This repo uses RunTrim as the guarded AI coding control layer.",
1993
+ "",
1994
+ "## How to start an AI coding task",
1995
+ "",
1996
+ "```",
1997
+ 'runtrim go "<task>"',
1998
+ "```",
1999
+ "",
2000
+ "RunTrim creates a scoped contract, loads project memory, and generates a guarded prompt.",
2001
+ "",
2002
+ "## How to use your agent",
2003
+ "",
2004
+ "Paste the guarded prompt into Claude Code, Codex, Cursor, or any other coding agent.",
2005
+ "",
2006
+ "## After edits",
2007
+ "",
2008
+ "```",
2009
+ "runtrim finish",
2010
+ "```",
2011
+ "",
2012
+ "RunTrim checks changed files, detects drift, scores risk, and saves the run report.",
2013
+ "",
2014
+ "## If you are an AI coding agent",
2015
+ "",
2016
+ "1. Read `.runtrim/contracts/latest.md`.",
2017
+ " - If `Status: active` \u2014 a live task exists. Follow the contract strictly.",
2018
+ ' - If `Status: none` \u2014 no active task. Ask the user to run `runtrim go "<task>"` first.',
2019
+ "2. Do not assume any prior task is still active.",
2020
+ "3. Stay inside the allowed scope defined in the contract.",
2021
+ "4. Stop and ask before touching any forbidden area.",
2022
+ "5. Do not read or write `.env` files or secrets.",
2023
+ "6. After editing, tell the user to run: `runtrim finish`",
2024
+ "",
2025
+ "---",
2026
+ `Protocol: runtrim init. Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
2027
+ ];
2028
+ fs7.writeFileSync(path7.join(cwd, "RUNTRIM.md"), lines.join("\n"), "utf-8");
2029
+ }
2030
+ function writeRestingContract(cwd = process.cwd()) {
2031
+ const dir = path7.join(getConfigDir(cwd), "contracts");
2032
+ if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
2033
+ const content = [
2034
+ "# RunTrim Contract",
2035
+ "",
2036
+ "Status: none",
2037
+ "",
2038
+ "No active RunTrim contract.",
2039
+ "",
2040
+ "Start one with:",
2041
+ "",
2042
+ "```",
2043
+ 'runtrim go "<your task>"',
2044
+ "```",
2045
+ "",
2046
+ "---",
2047
+ `Reset by runtrim finish. Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
2048
+ ].join("\n");
2049
+ fs7.writeFileSync(path7.join(dir, "latest.md"), content, "utf-8");
2050
+ }
2051
+ function writeRestingMemory(cwd = process.cwd()) {
2052
+ const dir = path7.join(getConfigDir(cwd), "memory");
2053
+ if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
2054
+ const content = [
2055
+ "# RunTrim Memory",
2056
+ "",
2057
+ "Status: none",
2058
+ "",
2059
+ "No active RunTrim session.",
2060
+ "",
2061
+ "Project baseline is in `.runtrim/memory/baseline.md`.",
2062
+ "",
2063
+ "Start a new session with:",
2064
+ "",
2065
+ "```",
2066
+ 'runtrim go "<your task>"',
2067
+ "```",
2068
+ "",
2069
+ "---",
2070
+ `Reset by runtrim finish. Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
2071
+ ].join("\n");
2072
+ fs7.writeFileSync(path7.join(dir, "current.md"), content, "utf-8");
2073
+ }
2074
+ function archiveContract(cwd, runId) {
2075
+ const contractsDir = path7.join(getConfigDir(cwd), "contracts");
2076
+ const latestPath = path7.join(contractsDir, "latest.md");
2077
+ if (!fs7.existsSync(latestPath)) return;
2078
+ const content = fs7.readFileSync(latestPath, "utf-8");
2079
+ if (content.includes("Status: none")) return;
2080
+ const archiveDir = path7.join(contractsDir, "archive");
2081
+ if (!fs7.existsSync(archiveDir)) fs7.mkdirSync(archiveDir, { recursive: true });
2082
+ fs7.writeFileSync(path7.join(archiveDir, `${runId}.md`), content, "utf-8");
2083
+ }
2084
+ function archiveMemory(cwd, runId) {
2085
+ const memoryDir = path7.join(getConfigDir(cwd), "memory");
2086
+ const currentPath = path7.join(memoryDir, "current.md");
2087
+ if (!fs7.existsSync(currentPath)) return;
2088
+ const content = fs7.readFileSync(currentPath, "utf-8");
2089
+ if (content.includes("Status: none")) return;
2090
+ const archiveDir = path7.join(memoryDir, "archive");
2091
+ if (!fs7.existsSync(archiveDir)) fs7.mkdirSync(archiveDir, { recursive: true });
2092
+ fs7.writeFileSync(path7.join(archiveDir, `${runId}.md`), content, "utf-8");
2093
+ }
2094
+ function removeBridgeBlock(filePath) {
2095
+ if (!fs7.existsSync(filePath)) return false;
2096
+ const content = fs7.readFileSync(filePath, "utf-8");
2097
+ const startIdx = content.indexOf(BRIDGE_START);
2098
+ const endIdx = content.indexOf(BRIDGE_END);
2099
+ if (startIdx === -1 || endIdx === -1) return false;
2100
+ const before = content.slice(0, startIdx).trimEnd();
2101
+ const after = content.slice(endIdx + BRIDGE_END.length).replace(/^\n+/, "\n");
2102
+ const newContent = (before + after).trimEnd() + "\n";
2103
+ if (newContent === content) return false;
2104
+ fs7.writeFileSync(filePath, newContent, "utf-8");
2105
+ return true;
2106
+ }
1997
2107
  function writeContractFile(ctx, cwd = process.cwd()) {
1998
2108
  const dir = path7.join(getConfigDir(cwd), "contracts");
1999
2109
  if (!fs7.existsSync(dir)) fs7.mkdirSync(dir, { recursive: true });
@@ -2001,13 +2111,15 @@ function writeContractFile(ctx, cwd = process.cwd()) {
2001
2111
  const forbidLines = ctx.forbiddenScope.length > 0 ? ctx.forbiddenScope.map((s) => `- ${s}`) : ["- .env* files", "- auth logic", "- database schema / migrations", "- billing and payment logic"];
2002
2112
  const stopLines = ctx.stopConditions.length > 0 ? ctx.stopConditions.map((s) => `- ${s}`) : [
2003
2113
  "- Stop if scope must expand beyond the task.",
2004
- "- Stop if a forbidden area needs to be touched.",
2114
+ "- Stop and ask before touching any forbidden area.",
2005
2115
  "- Stop if more than 5 files require changes."
2006
2116
  ];
2007
2117
  const verifyLines = ctx.verificationSteps.length > 0 ? ctx.verificationSteps.map((s) => `- ${s}`) : ["- Verify the behavior described in the task.", "- Check no unrelated files changed."];
2008
2118
  const lines = [
2009
2119
  "# RunTrim Active Contract",
2010
2120
  "",
2121
+ "Status: active",
2122
+ "",
2011
2123
  `Task: ${ctx.task}`,
2012
2124
  `Goal: ${ctx.goal}`,
2013
2125
  `Risk: ${ctx.riskLevel}`,
@@ -2039,6 +2151,8 @@ function writeMemoryFile(ctx, cwd = process.cwd()) {
2039
2151
  const lines = [
2040
2152
  "# RunTrim Memory Pack",
2041
2153
  "",
2154
+ "Status: active",
2155
+ "",
2042
2156
  `Project: ${ctx.projectName}`,
2043
2157
  `Current task: ${ctx.task}`,
2044
2158
  `Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
@@ -2067,15 +2181,17 @@ function writeBridgeInstructions(cwd = process.cwd()) {
2067
2181
  "",
2068
2182
  "## Before editing",
2069
2183
  "",
2070
- "Read these files first:",
2071
- "- RUNTRIM.md",
2072
- "- .runtrim/contracts/latest.md",
2073
- "- .runtrim/memory/current.md",
2184
+ "1. Read `RUNTRIM.md`.",
2185
+ "2. Read `.runtrim/contracts/latest.md`.",
2186
+ " - If `Status: active` \u2014 follow the contract strictly.",
2187
+ ' - If `Status: none` \u2014 stop. Ask the user to run `runtrim go "<task>"` first.',
2188
+ "3. If the contract is active, read `.runtrim/memory/current.md` for session context.",
2189
+ " If no active session, read `.runtrim/memory/baseline.md` for project baseline.",
2074
2190
  "",
2075
2191
  "## During editing",
2076
2192
  "",
2077
2193
  "- Stay within the allowed scope defined in the contract.",
2078
- "- If a change requires touching a forbidden area, STOP and tell the user.",
2194
+ "- Stop and ask the user before touching any forbidden area.",
2079
2195
  "- Make minimal, targeted changes only.",
2080
2196
  "- Do not refactor, rename, or reorganize outside the task scope.",
2081
2197
  "- Do not add console.log statements or debug artifacts.",
@@ -2088,56 +2204,10 @@ function writeBridgeInstructions(cwd = process.cwd()) {
2088
2204
  "- Do not run runtrim commands yourself unless explicitly asked.",
2089
2205
  "",
2090
2206
  "---",
2091
- "Generated by RunTrim Bridge. Do not edit manually."
2207
+ "Generated by RunTrim. Do not edit manually."
2092
2208
  ];
2093
2209
  fs7.writeFileSync(path7.join(dir, "agent-instructions.md"), lines.join("\n"), "utf-8");
2094
2210
  }
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
2211
  function writeBridgeFiles(ctx, cwd) {
2142
2212
  const written = [];
2143
2213
  const managedPaths = [];
@@ -2145,22 +2215,18 @@ function writeBridgeFiles(ctx, cwd) {
2145
2215
  managedPaths.push(relativePath);
2146
2216
  written.push(label != null ? label : relativePath);
2147
2217
  };
2148
- writeRootProtocolFile(ctx, cwd);
2149
- track("RUNTRIM.md");
2150
2218
  writeContractFile(ctx, cwd);
2151
2219
  track(".runtrim/contracts/latest.md");
2152
2220
  writeMemoryFile(ctx, cwd);
2153
2221
  track(".runtrim/memory/current.md");
2154
2222
  writeBridgeInstructions(cwd);
2155
2223
  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
2224
  return { written, managedPaths };
2159
2225
  }
2160
2226
  function buildBridgePrompt(contractText, ctx) {
2161
2227
  const allowedList = ctx.allowedScope.length > 0 ? ctx.allowedScope.map((s) => ` - ${s}`).join("\n") : " - Defined in .runtrim/contracts/latest.md";
2162
2228
  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.";
2229
+ 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 and ask before touching any forbidden area.";
2164
2230
  const header = [
2165
2231
  "RUNTRIM BRIDGE SESSION",
2166
2232
  `Task: ${ctx.task}`,
@@ -2182,7 +2248,7 @@ function buildBridgePrompt(contractText, ctx) {
2182
2248
  " - Read RUNTRIM.md and .runtrim/contracts/latest.md before touching any file.",
2183
2249
  " - Inspect first. List relevant files before opening them.",
2184
2250
  " - Make the minimal change required. No unrelated refactors.",
2185
- " - If scope must expand, stop immediately and tell the user.",
2251
+ " - Stop and ask before touching any forbidden area.",
2186
2252
  " - After editing, tell the user which files changed. They will run: runtrim finish",
2187
2253
  ""
2188
2254
  ].join("\n");
@@ -4227,55 +4293,7 @@ function installProtocol(cwd, baseline, opts = {}) {
4227
4293
  const testCmd = (_f = (_e = scripts["test"]) != null ? _e : scripts["test:run"]) != null ? _f : null;
4228
4294
  const runtrimMdPath = path10.join(cwd, "RUNTRIM.md");
4229
4295
  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");
4296
+ writeCanonicalRuntrimMd(cwd, baseline.projectName);
4279
4297
  const projectJsonPath = path10.join(configDir, "project.json");
4280
4298
  const projectJsonExists = fs10.existsSync(projectJsonPath);
4281
4299
  const projectJson = {
@@ -4369,8 +4387,9 @@ function installProtocol(cwd, baseline, opts = {}) {
4369
4387
  for (const filename of agentTargets) {
4370
4388
  const filePath = path10.join(cwd, filename);
4371
4389
  if (fs10.existsSync(filePath)) {
4390
+ removeBridgeBlock(filePath);
4372
4391
  const result = upsertProtocolBlock(filePath);
4373
- if (result !== "skipped") agentResults.push({ file: filename, result });
4392
+ agentResults.push({ file: filename, result: result === "skipped" ? "unchanged" : result });
4374
4393
  } else if (opts.agentFiles) {
4375
4394
  createMinimalAgentPointerFile(filePath, filename);
4376
4395
  agentResults.push({ file: filename, result: "created" });
@@ -4416,6 +4435,18 @@ function installProtocol(cwd, baseline, opts = {}) {
4416
4435
  fs10.writeFileSync(mdcPath, cursorMdc, "utf-8");
4417
4436
  cursorResult = existed ? "updated" : "created";
4418
4437
  }
4438
+ const contractsDir = path10.join(configDir, "contracts");
4439
+ const latestContractPath = path10.join(contractsDir, "latest.md");
4440
+ const contractIsStale = fs10.existsSync(latestContractPath) && !fs10.readFileSync(latestContractPath, "utf-8").includes("Status: none");
4441
+ if (!fs10.existsSync(latestContractPath) || contractIsStale) {
4442
+ writeRestingContract(cwd);
4443
+ }
4444
+ const memoryDir = path10.join(configDir, "memory");
4445
+ const currentMemoryPath = path10.join(memoryDir, "current.md");
4446
+ const memoryIsStale = fs10.existsSync(currentMemoryPath) && !fs10.readFileSync(currentMemoryPath, "utf-8").includes("Status: none");
4447
+ if (!fs10.existsSync(currentMemoryPath) || memoryIsStale) {
4448
+ writeRestingMemory(cwd);
4449
+ }
4419
4450
  return {
4420
4451
  runtrimMd: runtrimMdExists ? "updated" : "created",
4421
4452
  projectJson: projectJsonExists ? "updated" : "created",
@@ -5151,7 +5182,7 @@ program.command("prepare <task>").description("Prepare a guarded prompt without
5151
5182
  }
5152
5183
  );
5153
5184
  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;
5185
+ var _a2, _b, _c, _d, _e;
5155
5186
  const cwd = process.cwd();
5156
5187
  const allowed = await ensureRepoAllowedForFree(cwd);
5157
5188
  if (!allowed) return;
@@ -5258,33 +5289,19 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
5258
5289
  const copied = doCopy ? await copyToClipboardSafe(fullPrompt) : false;
5259
5290
  if (options.monitor) void tryLaunchPanelMonitorDetached(cwd);
5260
5291
  updateRun(run.id, { bridgeManagedFiles: bridgeManagedPaths }, cwd);
5261
- let synced = false;
5292
+ let cloudSync = { status: "skipped_no_token" };
5262
5293
  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
- }
5294
+ cloudSync = await syncRunsToCloud({
5295
+ cwd,
5296
+ config,
5297
+ projectName,
5298
+ projectAudit: projectAudit != null ? projectAudit : null,
5299
+ memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
5300
+ runs: loadAllRuns(cwd),
5301
+ markPendingRunIds: [run.id]
5302
+ });
5286
5303
  }
5287
- const riskColor = (_g = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[bridgeCtx.riskLevel]) != null ? _g : chalk.white;
5304
+ const riskColor = (_e = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[bridgeCtx.riskLevel]) != null ? _e : chalk.white;
5288
5305
  console.log("");
5289
5306
  console.log(GO_ACCENT.bold("RunTrim go"));
5290
5307
  console.log("");
@@ -5314,7 +5331,15 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
5314
5331
  }
5315
5332
  if (options.sync !== false) {
5316
5333
  console.log(GO_ACCENT.bold("Cloud sync"));
5317
- console.log(DIM(" ") + (synced ? chalk.white("Synced.") : DIM("Skipped. Run runtrim login to connect your dashboard.")));
5334
+ if (cloudSync.status === "synced") {
5335
+ console.log(chalk.white(" Started run synced."));
5336
+ } else if (cloudSync.status === "failed") {
5337
+ console.log(chalk.yellow(" Failed. Run saved locally. Use runtrim sync later."));
5338
+ } else if (cloudSync.status === "skipped_no_token" || cloudSync.status === "skipped_invalid_token") {
5339
+ console.log(DIM(" Skipped. Run runtrim login to connect your dashboard."));
5340
+ } else {
5341
+ console.log(DIM(" Skipped."));
5342
+ }
5318
5343
  console.log("");
5319
5344
  }
5320
5345
  console.log(GO_ACCENT.bold("Prompt"));
@@ -6235,6 +6260,54 @@ function resolveApiBase(config) {
6235
6260
  return "https://www.runtrim.com";
6236
6261
  }
6237
6262
  }
6263
+ async function syncRunsToCloud(input) {
6264
+ var _a2, _b, _c;
6265
+ const { cwd, config, projectName, projectAudit, memoryMarkdown, runs, markPendingRunIds } = input;
6266
+ const globalAuth = loadGlobalAuth();
6267
+ const rawToken = (_b = (_a2 = globalAuth == null ? void 0 : globalAuth.token) != null ? _a2 : config.syncToken) != null ? _b : null;
6268
+ if (!rawToken) return { status: "skipped_no_token" };
6269
+ if (!rawToken.startsWith("rt_live_")) return { status: "skipped_invalid_token" };
6270
+ if (runs.length === 0) return { status: "skipped_no_runs" };
6271
+ try {
6272
+ const payload = buildSyncPayload({
6273
+ cwd,
6274
+ projectName,
6275
+ config,
6276
+ projectAudit: projectAudit != null ? projectAudit : null,
6277
+ memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
6278
+ runs
6279
+ });
6280
+ const apiBase = resolveApiBase(config);
6281
+ const res = await fetch(`${apiBase}/api/sync`, {
6282
+ method: "POST",
6283
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${rawToken}` },
6284
+ body: JSON.stringify(payload)
6285
+ });
6286
+ const body = await res.json().catch(() => ({}));
6287
+ if (!res.ok || !body.ok) {
6288
+ if (markPendingRunIds && markPendingRunIds.length > 0) {
6289
+ for (const id of markPendingRunIds) updateRun(id, { pendingSync: true }, cwd);
6290
+ }
6291
+ return {
6292
+ status: "failed",
6293
+ error: body.error,
6294
+ details: body.details
6295
+ };
6296
+ }
6297
+ for (const run of runs) {
6298
+ if (run.pendingSync) updateRun(run.id, { pendingSync: false }, cwd);
6299
+ }
6300
+ return {
6301
+ status: "synced",
6302
+ syncedRuns: (_c = body.syncedRuns) != null ? _c : payload.runs.length
6303
+ };
6304
+ } catch (e) {
6305
+ if (markPendingRunIds && markPendingRunIds.length > 0) {
6306
+ for (const id of markPendingRunIds) updateRun(id, { pendingSync: true }, cwd);
6307
+ }
6308
+ return { status: "failed" };
6309
+ }
6310
+ }
6238
6311
  program.command("login").description("Connect this machine to your RunTrim cloud account").option("--token <token>", "CLI token (skip interactive prompt)").action(async (opts) => {
6239
6312
  var _a2, _b, _c, _d, _e;
6240
6313
  console.log("");
@@ -6306,7 +6379,7 @@ program.command("login").description("Connect this machine to your RunTrim cloud
6306
6379
  console.log("");
6307
6380
  });
6308
6381
  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;
6382
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
6310
6383
  const cwd = process.cwd();
6311
6384
  console.log("");
6312
6385
  console.log(GO_ACCENT.bold("RunTrim finish"));
@@ -6411,41 +6484,36 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
6411
6484
  const freshRuns = loadAllRuns(cwd);
6412
6485
  const updatedRun = (_k = freshRuns.find((r) => r.id === activeRun.id)) != null ? _k : activeRun;
6413
6486
  writeMemoryFromRuns(updatedRun, freshRuns, config, cwd);
6414
- let synced = false;
6487
+ archiveContract(cwd, activeRun.id);
6488
+ archiveMemory(cwd, activeRun.id);
6489
+ writeCanonicalRuntrimMd(cwd, projectName);
6490
+ writeRestingContract(cwd);
6491
+ writeRestingMemory(cwd);
6492
+ const bridgeRemovals = [];
6493
+ if (removeBridgeBlock(path10.join(cwd, "CLAUDE.md"))) bridgeRemovals.push("CLAUDE.md");
6494
+ if (removeBridgeBlock(path10.join(cwd, "AGENTS.md"))) bridgeRemovals.push("AGENTS.md");
6495
+ let cloudSync = { status: "skipped_no_token" };
6415
6496
  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_")) {
6497
+ const memoryMarkdown = (() => {
6419
6498
  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;
6499
+ return readMemory(cwd);
6442
6500
  } catch (e) {
6501
+ return null;
6443
6502
  }
6444
- }
6503
+ })();
6504
+ cloudSync = await syncRunsToCloud({
6505
+ cwd,
6506
+ config,
6507
+ projectName,
6508
+ projectAudit: projectAudit != null ? projectAudit : null,
6509
+ memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
6510
+ runs: freshRuns,
6511
+ markPendingRunIds: [activeRun.id]
6512
+ });
6445
6513
  }
6446
6514
  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;
6515
+ const riskAfter = (_l = activeRun.contract.wasteRiskAfter) != null ? _l : "medium";
6516
+ const riskColor = (_m = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[riskAfter]) != null ? _m : chalk.white;
6449
6517
  console.log(GO_ACCENT.bold("Run"));
6450
6518
  console.log(chalk.white(" " + truncate(activeRun.task, 70)));
6451
6519
  console.log("");
@@ -6498,31 +6566,35 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
6498
6566
  }
6499
6567
  if (options.sync !== false) {
6500
6568
  console.log(GO_ACCENT.bold("Cloud sync"));
6501
- console.log(DIM(" ") + (synced ? chalk.white("Completed.") : DIM("Skipped. Run runtrim login to connect your dashboard.")));
6569
+ if (cloudSync.status === "synced") {
6570
+ console.log(chalk.white(" Completed run synced."));
6571
+ } else if (cloudSync.status === "failed") {
6572
+ console.log(chalk.yellow(" Failed. Run saved locally. Use runtrim sync later."));
6573
+ } else if (cloudSync.status === "skipped_no_token" || cloudSync.status === "skipped_invalid_token") {
6574
+ console.log(DIM(" Skipped. Run runtrim login to connect your dashboard."));
6575
+ } else {
6576
+ console.log(DIM(" Skipped."));
6577
+ }
6502
6578
  console.log("");
6503
6579
  }
6580
+ console.log(GO_ACCENT.bold("Protocol"));
6581
+ console.log(DIM(" ") + chalk.white("RUNTRIM.md restored"));
6582
+ console.log(DIM(" ") + chalk.white("latest contract archived"));
6583
+ console.log(DIM(" ") + chalk.white("current memory reset"));
6584
+ if (bridgeRemovals.length > 0) {
6585
+ for (const f of bridgeRemovals) {
6586
+ console.log(DIM(" ") + chalk.white(`${f} bridge block removed`));
6587
+ }
6588
+ }
6589
+ console.log("");
6504
6590
  });
6505
6591
  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;
6592
+ var _a2, _b;
6507
6593
  const cwd = process.cwd();
6508
6594
  console.log("");
6509
6595
  console.log(BOLD("RunTrim") + DIM(" cloud sync"));
6510
6596
  console.log("");
6511
- const globalAuth = loadGlobalAuth();
6512
6597
  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
6598
  const apiBase = resolveApiBase(config);
6527
6599
  const syncUrl = `${apiBase}/api/sync`;
6528
6600
  const runs = loadAllRuns(cwd);
@@ -6533,6 +6605,7 @@ program.command("sync").description("Sync local run history and project memory t
6533
6605
  return;
6534
6606
  }
6535
6607
  const projectAudit = loadProjectAudit(cwd);
6608
+ const projectName = (_a2 = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _a2 : path10.basename(cwd);
6536
6609
  const memoryMarkdown = (() => {
6537
6610
  try {
6538
6611
  return readMemory(cwd);
@@ -6542,7 +6615,7 @@ program.command("sync").description("Sync local run history and project memory t
6542
6615
  })();
6543
6616
  const payload = buildSyncPayload({
6544
6617
  cwd,
6545
- projectName: (_c = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _c : path10.basename(cwd),
6618
+ projectName,
6546
6619
  config,
6547
6620
  projectAudit: projectAudit != null ? projectAudit : null,
6548
6621
  memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
@@ -6553,41 +6626,46 @@ program.command("sync").description("Sync local run history and project memory t
6553
6626
  console.log(DIM(" API ") + chalk.white(syncUrl));
6554
6627
  console.log("");
6555
6628
  if (opts.dryRun) {
6556
- console.log(ACCENT.bold(" Dry run \u2014 nothing uploaded."));
6629
+ console.log(ACCENT.bold(" Dry run \uFFFD nothing uploaded."));
6557
6630
  console.log("");
6558
6631
  return;
6559
6632
  }
6560
6633
  const spinner = oraFactory({ text: " Syncing...", color: "blue" }).start();
6561
6634
  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)
6635
+ const result = await syncRunsToCloud({
6636
+ cwd,
6637
+ config,
6638
+ projectName,
6639
+ projectAudit: projectAudit != null ? projectAudit : null,
6640
+ memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
6641
+ runs
6569
6642
  });
6570
- const body = await res.json();
6571
- if (!res.ok || !body.ok) {
6643
+ if (result.status !== "synced") {
6572
6644
  spinner.fail(" Sync failed.");
6573
6645
  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
- }
6646
+ if (result.status === "skipped_no_token") {
6647
+ console.log(chalk.yellow(" No CLI token found."));
6648
+ console.log(DIM(" Run ") + GO_ACCENT("runtrim login") + DIM(" to connect cloud sync."));
6649
+ console.log(DIM(" Local CLI still works without a token."));
6650
+ } else if (result.status === "skipped_invalid_token") {
6651
+ console.log(chalk.yellow(" Stored token format is invalid. Re-run: runtrim login"));
6652
+ } else if (result.error) {
6653
+ console.log(chalk.red(" Error: ") + chalk.white(result.error));
6654
+ if (result.details) console.log(chalk.red(" Details: ") + chalk.white(result.details));
6655
+ } else {
6656
+ console.log(chalk.yellow(" Failed. Run saved locally. Use runtrim sync later."));
6579
6657
  }
6580
6658
  console.log("");
6581
6659
  return;
6582
6660
  }
6583
6661
  spinner.succeed(" Sync complete.");
6584
6662
  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"));
6663
+ const syncedRuns = (_b = result.syncedRuns) != null ? _b : payload.runs.length;
6664
+ console.log(ACCENT.bold(" Synced") + chalk.white(` ${syncedRuns} run${syncedRuns === 1 ? "" : "s"}`));
6587
6665
  console.log("");
6588
6666
  console.log(DIM(" View at ") + GO_ACCENT(`${apiBase}/app`));
6589
6667
  console.log("");
6590
- } catch (err) {
6668
+ } catch (e) {
6591
6669
  spinner.fail(" Network error. Check your connection.");
6592
6670
  console.log("");
6593
6671
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runtrim",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "CLI guard layer that scopes AI coding runs before they waste tokens.",
5
5
  "license": "MIT",
6
6
  "author": "RunTrim",