runtrim 0.1.8 → 0.1.10

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 +503 -105
  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");
@@ -4165,13 +4231,239 @@ program.command("start").description("Guided RunTrim onboarding and daily loop")
4165
4231
  console.log("");
4166
4232
  }
4167
4233
  });
4168
- program.command("init").description("Initialize RunTrim in the current project").option("--refresh", "Refresh baseline audit/rules/memory without overwriting config").action(async (options) => {
4234
+ var PROTOCOL_BLOCK_START = "<!-- RUNTRIM_PROTOCOL_START -->";
4235
+ var PROTOCOL_BLOCK_END = "<!-- RUNTRIM_PROTOCOL_END -->";
4236
+ var PROTOCOL_POINTER_BLOCK = `
4237
+ ${PROTOCOL_BLOCK_START}
4238
+ This repo uses RunTrim as the guarded AI coding protocol.
4239
+ Before editing code, read RUNTRIM.md.
4240
+ Start every task with: runtrim go "<task>"
4241
+ Stay inside .runtrim/contracts/latest.md.
4242
+ After edits, ask the user to run: runtrim finish
4243
+ ${PROTOCOL_BLOCK_END}
4244
+ `;
4245
+ function upsertProtocolBlock(filePath) {
4246
+ if (!fs10.existsSync(filePath)) return "skipped";
4247
+ const content = fs10.readFileSync(filePath, "utf-8");
4248
+ const startIdx = content.indexOf(PROTOCOL_BLOCK_START);
4249
+ const endIdx = content.indexOf(PROTOCOL_BLOCK_END);
4250
+ if (startIdx !== -1 && endIdx !== -1) {
4251
+ const before = content.slice(0, startIdx);
4252
+ const after = content.slice(endIdx + PROTOCOL_BLOCK_END.length);
4253
+ const newContent = before + PROTOCOL_POINTER_BLOCK.trimStart() + after.replace(/^\n/, "");
4254
+ if (newContent === content) return "unchanged";
4255
+ fs10.writeFileSync(filePath, newContent, "utf-8");
4256
+ return "updated";
4257
+ }
4258
+ fs10.writeFileSync(filePath, content.trimEnd() + "\n" + PROTOCOL_POINTER_BLOCK, "utf-8");
4259
+ return "updated";
4260
+ }
4261
+ function createMinimalAgentPointerFile(filePath, filename) {
4262
+ const label = filename === "CLAUDE.md" ? "Claude Code" : "AI agents";
4263
+ const content = [
4264
+ `# ${label} Instructions`,
4265
+ "",
4266
+ "This repo uses RunTrim as the guarded AI coding protocol.",
4267
+ "Read RUNTRIM.md before editing any code.",
4268
+ "",
4269
+ PROTOCOL_POINTER_BLOCK.trim(),
4270
+ ""
4271
+ ].join("\n");
4272
+ fs10.writeFileSync(filePath, content, "utf-8");
4273
+ }
4274
+ function installProtocol(cwd, baseline, opts = {}) {
4275
+ var _a2, _b, _c, _d, _e, _f, _g;
4276
+ const configDir = getConfigDir(cwd);
4277
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4278
+ const extraFolders = [
4279
+ path10.join(configDir, "contracts"),
4280
+ path10.join(configDir, "memory"),
4281
+ path10.join(configDir, "bridge"),
4282
+ path10.join(configDir, "reports")
4283
+ ];
4284
+ const createdFolders = [];
4285
+ for (const dir of extraFolders) {
4286
+ if (!fs10.existsSync(dir)) {
4287
+ fs10.mkdirSync(dir, { recursive: true });
4288
+ createdFolders.push(dir.replace(cwd + path10.sep, "").replace(/\\/g, "/"));
4289
+ }
4290
+ }
4291
+ const scripts = (_a2 = baseline.scripts) != null ? _a2 : {};
4292
+ const buildCmd = (_d = (_c = (_b = scripts["build"]) != null ? _b : scripts["build:web"]) != null ? _c : scripts["build:all"]) != null ? _d : null;
4293
+ const testCmd = (_f = (_e = scripts["test"]) != null ? _e : scripts["test:run"]) != null ? _f : null;
4294
+ const runtrimMdPath = path10.join(cwd, "RUNTRIM.md");
4295
+ const runtrimMdExists = fs10.existsSync(runtrimMdPath);
4296
+ writeCanonicalRuntrimMd(cwd, baseline.projectName);
4297
+ const projectJsonPath = path10.join(configDir, "project.json");
4298
+ const projectJsonExists = fs10.existsSync(projectJsonPath);
4299
+ const projectJson = {
4300
+ name: baseline.projectName,
4301
+ stack: baseline.detectedStack,
4302
+ packageManager: baseline.packageManager,
4303
+ buildCommand: buildCmd,
4304
+ testCommand: testCmd,
4305
+ detectedAt: now
4306
+ };
4307
+ fs10.writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2), "utf-8");
4308
+ const policiesPath = path10.join(configDir, "policies.json");
4309
+ const policiesJsonExists = fs10.existsSync(policiesPath);
4310
+ const detectedSensitive = ((_g = baseline.riskSurfaces) != null ? _g : []).map((s) => s.type.toLowerCase());
4311
+ const policies = {
4312
+ version: 1,
4313
+ protected: [
4314
+ ".env*",
4315
+ "secrets",
4316
+ "*.key",
4317
+ "*.pem",
4318
+ "auth/**",
4319
+ "middleware.ts",
4320
+ "prisma/schema.prisma",
4321
+ "prisma/migrations/**",
4322
+ "database/migrations/**",
4323
+ "stripe/**",
4324
+ "billing/**",
4325
+ "payment/**",
4326
+ "webhooks/**",
4327
+ "package-lock.json",
4328
+ "pnpm-lock.yaml",
4329
+ "yarn.lock",
4330
+ ".next/**",
4331
+ "dist/**",
4332
+ "build/**",
4333
+ "node_modules/**"
4334
+ ],
4335
+ sensitive: [
4336
+ "auth",
4337
+ "billing",
4338
+ "payment",
4339
+ "middleware",
4340
+ "database",
4341
+ "schema",
4342
+ "migrations",
4343
+ "env",
4344
+ "secrets",
4345
+ "webhooks",
4346
+ ...detectedSensitive.filter((s) => !["auth", "billing", "payment", "middleware", "database", "env", "secrets", "webhooks"].includes(s))
4347
+ ],
4348
+ note: "These areas require explicit task scope before any agent edits.",
4349
+ updatedAt: now
4350
+ };
4351
+ fs10.writeFileSync(policiesPath, JSON.stringify(policies, null, 2), "utf-8");
4352
+ const baselineMdPath = path10.join(configDir, "memory", "baseline.md");
4353
+ const baselineMdExists = fs10.existsSync(baselineMdPath);
4354
+ const protectedList = policies.protected.slice(0, 10).map((p) => `- ${p}`).join("\n");
4355
+ const stackLine = baseline.detectedStack.join(", ") || "unknown";
4356
+ const baselineMd = [
4357
+ "# RunTrim Memory \u2014 Baseline",
4358
+ "",
4359
+ `Project: ${baseline.projectName}`,
4360
+ `Stack: ${stackLine}`,
4361
+ `Package manager: ${baseline.packageManager}`,
4362
+ ...buildCmd ? [`Build: ${buildCmd}`] : [],
4363
+ ...testCmd ? [`Test: ${testCmd}`] : [],
4364
+ "",
4365
+ "## Protected areas",
4366
+ "",
4367
+ protectedList,
4368
+ "",
4369
+ "## Project rules",
4370
+ "",
4371
+ '- Start every AI task with: runtrim go "<task>"',
4372
+ "- Stay inside the scoped contract.",
4373
+ "- Run runtrim finish after agent edits.",
4374
+ "- No unrelated refactors during a task.",
4375
+ "- Never touch .env files.",
4376
+ "",
4377
+ "## Prior agent decisions",
4378
+ "",
4379
+ "No prior runs recorded. This is the baseline for this project.",
4380
+ "",
4381
+ "---",
4382
+ `Created by runtrim init. Updated: ${now}`
4383
+ ].join("\n");
4384
+ fs10.writeFileSync(baselineMdPath, baselineMd, "utf-8");
4385
+ const agentResults = [];
4386
+ const agentTargets = ["CLAUDE.md", "AGENTS.md"];
4387
+ for (const filename of agentTargets) {
4388
+ const filePath = path10.join(cwd, filename);
4389
+ if (fs10.existsSync(filePath)) {
4390
+ removeBridgeBlock(filePath);
4391
+ const result = upsertProtocolBlock(filePath);
4392
+ agentResults.push({ file: filename, result: result === "skipped" ? "unchanged" : result });
4393
+ } else if (opts.agentFiles) {
4394
+ createMinimalAgentPointerFile(filePath, filename);
4395
+ agentResults.push({ file: filename, result: "created" });
4396
+ } else {
4397
+ agentResults.push({ file: filename, result: "skipped" });
4398
+ }
4399
+ }
4400
+ let cursorResult = "skipped";
4401
+ const cursorDir = path10.join(cwd, ".cursor");
4402
+ const cursorExists = fs10.existsSync(cursorDir);
4403
+ if (opts.cursor || cursorExists) {
4404
+ const rulesDir = path10.join(cursorDir, "rules");
4405
+ const mdcPath = path10.join(rulesDir, "runtrim.mdc");
4406
+ if (!fs10.existsSync(rulesDir)) fs10.mkdirSync(rulesDir, { recursive: true });
4407
+ const existed = fs10.existsSync(mdcPath);
4408
+ const cursorMdc = [
4409
+ "---",
4410
+ "description: RunTrim guarded AI coding protocol",
4411
+ "alwaysApply: true",
4412
+ "---",
4413
+ "",
4414
+ "# RunTrim Protocol",
4415
+ "",
4416
+ "This repo uses RunTrim as the guarded AI coding control layer.",
4417
+ "",
4418
+ "## Before editing any code",
4419
+ "",
4420
+ "1. Read `RUNTRIM.md` in the repo root.",
4421
+ "2. If `.runtrim/contracts/latest.md` exists, read the active contract.",
4422
+ "",
4423
+ "## Rules",
4424
+ "",
4425
+ "- Stay inside the allowed scope defined in the contract.",
4426
+ "- Do not touch forbidden files or unrelated systems.",
4427
+ "- Stop immediately if scope must expand.",
4428
+ "- Do not read or write `.env` files.",
4429
+ "- Do not refactor outside the task scope.",
4430
+ "",
4431
+ "## After editing",
4432
+ "",
4433
+ "Tell the user to run: `runtrim finish`"
4434
+ ].join("\n");
4435
+ fs10.writeFileSync(mdcPath, cursorMdc, "utf-8");
4436
+ cursorResult = existed ? "updated" : "created";
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
+ }
4450
+ return {
4451
+ runtrimMd: runtrimMdExists ? "updated" : "created",
4452
+ projectJson: projectJsonExists ? "updated" : "created",
4453
+ policiesJson: policiesJsonExists ? "updated" : "created",
4454
+ baselineMd: baselineMdExists ? "updated" : "created",
4455
+ folders: createdFolders,
4456
+ agentFiles: agentResults,
4457
+ cursorRules: cursorResult
4458
+ };
4459
+ }
4460
+ 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) => {
4169
4461
  var _a2;
4170
4462
  const cwd = process.cwd();
4171
4463
  const allowed = await ensureRepoAllowedForFree(cwd);
4172
4464
  if (!allowed) return;
4173
4465
  console.log("");
4174
- console.log(BOLD("RunTrim") + DIM(" init"));
4466
+ console.log(GO_ACCENT.bold("RunTrim init"));
4175
4467
  console.log("");
4176
4468
  const initResult = await initializeRunTrim(cwd, {
4177
4469
  refresh: options.refresh,
@@ -4179,33 +4471,46 @@ program.command("init").description("Initialize RunTrim in the current project")
4179
4471
  });
4180
4472
  if (!initResult.ok) return;
4181
4473
  const baseline = (_a2 = loadProjectAudit(cwd)) != null ? _a2 : performBaselineProjectAudit(cwd, null);
4182
- const scriptNames = Object.keys(baseline.scripts);
4183
- const starterCreated = fs10.existsSync(path10.join(getConfigDir(cwd), "latest-prompt.md"));
4184
- console.log(ACCENT.bold(" RunTrim init"));
4185
- console.log("");
4186
- console.log(DIM(" Project detected"));
4187
- console.log(DIM(" Name ") + chalk.white(baseline.projectName));
4188
- console.log(DIM(" Stack ") + chalk.white(baseline.detectedStack.join(", ") || "unknown"));
4189
- console.log(DIM(" Package ") + chalk.white(baseline.packageManager));
4190
- console.log("");
4191
- console.log(DIM(" Scripts found"));
4192
- console.log(DIM(" ") + chalk.white(scriptNames.length ? scriptNames.join(", ") : "none"));
4474
+ const protocol = installProtocol(cwd, baseline, {
4475
+ agentFiles: options.agentFiles,
4476
+ cursor: options.cursor
4477
+ });
4478
+ const stackLine = baseline.detectedStack.length ? baseline.detectedStack.join(" + ") : "unknown stack";
4479
+ console.log(DIM(" Project"));
4480
+ console.log(chalk.white(" " + baseline.projectName));
4481
+ console.log(DIM(" " + stackLine));
4193
4482
  console.log("");
4194
- console.log(DIM(" Risk surfaces"));
4195
- for (const s of baseline.riskSurfaces.slice(0, 8)) {
4196
- console.log(DIM(" - ") + chalk.white(s.type));
4483
+ console.log(DIM(" Protocol"));
4484
+ const protocolFiles = [
4485
+ ["RUNTRIM.md", protocol.runtrimMd],
4486
+ [".runtrim/project.json", protocol.projectJson],
4487
+ [".runtrim/policies.json", protocol.policiesJson],
4488
+ [".runtrim/memory/baseline.md", protocol.baselineMd]
4489
+ ];
4490
+ for (const [file, result] of protocolFiles) {
4491
+ console.log(DIM(" ") + chalk.white(file.padEnd(34)) + DIM(result));
4197
4492
  }
4198
4493
  console.log("");
4199
- console.log(DIM(options.refresh ? " Files refreshed" : " Files created"));
4200
- console.log(DIM(" .runtrim/config.json"));
4201
- console.log(DIM(" .runtrim/project-audit.json"));
4202
- console.log(DIM(" .runtrim/rules.md"));
4203
- console.log(DIM(" .runtrim/memory.md"));
4204
- console.log(DIM(" .runtrim/runs/"));
4205
- if (starterCreated) console.log(DIM(" .runtrim/latest-prompt.md"));
4494
+ console.log(DIM(" Agent pointers"));
4495
+ for (const { file, result } of protocol.agentFiles) {
4496
+ const color = result === "skipped" ? DIM : chalk.white;
4497
+ console.log(DIM(" ") + color(file.padEnd(34)) + DIM(result));
4498
+ }
4499
+ if (protocol.cursorRules !== "skipped") {
4500
+ console.log(DIM(" ") + chalk.white(".cursor/rules/runtrim.mdc".padEnd(34)) + DIM(protocol.cursorRules));
4501
+ } else if (options.cursor) {
4502
+ console.log(DIM(" Cursor rules skipped (.cursor/ not found and --cursor not passed)"));
4503
+ }
4206
4504
  console.log("");
4505
+ if (protocol.folders.length > 0) {
4506
+ console.log(DIM(" Folders created"));
4507
+ for (const f of protocol.folders) {
4508
+ console.log(DIM(" " + f + "/"));
4509
+ }
4510
+ console.log("");
4511
+ }
4207
4512
  console.log(DIM(" Next"));
4208
- console.log(chalk.white(' runtrim prepare "describe your next AI coding task"'));
4513
+ console.log(chalk.white(' runtrim go "your first task"'));
4209
4514
  console.log("");
4210
4515
  });
4211
4516
  var agentCommand = program.command("agent").description("Show or configure local agent execution settings");
@@ -4877,7 +5182,7 @@ program.command("prepare <task>").description("Prepare a guarded prompt without
4877
5182
  }
4878
5183
  );
4879
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) => {
4880
- var _a2, _b, _c, _d;
5185
+ var _a2, _b, _c, _d, _e, _f, _g;
4881
5186
  const cwd = process.cwd();
4882
5187
  const allowed = await ensureRepoAllowedForFree(cwd);
4883
5188
  if (!allowed) return;
@@ -4886,6 +5191,38 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
4886
5191
  if (!initResult.ok) return;
4887
5192
  }
4888
5193
  const config = loadConfig(cwd);
5194
+ const globalAuth = loadGlobalAuth();
5195
+ const rawToken = (_b = (_a2 = globalAuth == null ? void 0 : globalAuth.token) != null ? _a2 : config.syncToken) != null ? _b : null;
5196
+ const apiBase = resolveApiBase(config);
5197
+ if (rawToken == null ? void 0 : rawToken.startsWith("rt_live_")) {
5198
+ let serverResult = null;
5199
+ try {
5200
+ const res = await fetch(`${apiBase}/api/cli/usage/bridge-run`, {
5201
+ method: "POST",
5202
+ headers: { Authorization: `Bearer ${rawToken}` }
5203
+ });
5204
+ if (res.ok) {
5205
+ const body = await res.json();
5206
+ serverResult = {
5207
+ allowed: body.allowed,
5208
+ plan: body.plan,
5209
+ used: (_c = body.usage.bridgeRunsUsed) != null ? _c : 0,
5210
+ limit: body.usage.bridgeRunsLimit
5211
+ };
5212
+ }
5213
+ } catch (e) {
5214
+ }
5215
+ if (serverResult !== null && !serverResult.allowed) {
5216
+ printBridgeLimitMessage(serverResult.used);
5217
+ return;
5218
+ }
5219
+ } else {
5220
+ const check = checkAndIncrementLocalUsage();
5221
+ if (!check.allowed) {
5222
+ printBridgeLimitMessage(check.used);
5223
+ return;
5224
+ }
5225
+ }
4889
5226
  const auditSpinner = oraFactory({ text: " Auditing task...", color: "blue" }).start();
4890
5227
  await new Promise((r) => setTimeout(r, 180));
4891
5228
  const audit = auditTask(task, config, cwd);
@@ -4917,7 +5254,7 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
4917
5254
  }
4918
5255
  const runs = loadAllRuns(cwd);
4919
5256
  const projectAudit = loadProjectAudit(cwd);
4920
- const projectName = (_a2 = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _a2 : path10.basename(cwd);
5257
+ const projectName = (_d = projectAudit == null ? void 0 : projectAudit.projectName) != null ? _d : path10.basename(cwd);
4921
5258
  const memoryMarkdown = (() => {
4922
5259
  try {
4923
5260
  return readMemory(cwd);
@@ -4954,9 +5291,9 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
4954
5291
  updateRun(run.id, { bridgeManagedFiles: bridgeManagedPaths }, cwd);
4955
5292
  let synced = false;
4956
5293
  if (options.sync !== false) {
4957
- const globalAuth = loadGlobalAuth();
4958
- const rawToken = (_c = (_b = globalAuth == null ? void 0 : globalAuth.token) != null ? _b : config.syncToken) != null ? _c : null;
4959
- if (rawToken == null ? void 0 : rawToken.startsWith("rt_live_")) {
5294
+ const globalAuth2 = loadGlobalAuth();
5295
+ const rawToken2 = (_f = (_e = globalAuth2 == null ? void 0 : globalAuth2.token) != null ? _e : config.syncToken) != null ? _f : null;
5296
+ if (rawToken2 == null ? void 0 : rawToken2.startsWith("rt_live_")) {
4960
5297
  try {
4961
5298
  const freshRuns = loadAllRuns(cwd);
4962
5299
  const payload = buildSyncPayload({
@@ -4967,10 +5304,10 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
4967
5304
  memoryMarkdown: memoryMarkdown != null ? memoryMarkdown : "",
4968
5305
  runs: freshRuns
4969
5306
  });
4970
- const apiBase = resolveApiBase(config);
4971
- const r = await fetch(`${apiBase}/api/sync`, {
5307
+ const apiBase2 = resolveApiBase(config);
5308
+ const r = await fetch(`${apiBase2}/api/sync`, {
4972
5309
  method: "POST",
4973
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${rawToken}` },
5310
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${rawToken2}` },
4974
5311
  body: JSON.stringify(payload)
4975
5312
  });
4976
5313
  synced = r.ok;
@@ -4978,7 +5315,7 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
4978
5315
  }
4979
5316
  }
4980
5317
  }
4981
- const riskColor = (_d = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[bridgeCtx.riskLevel]) != null ? _d : chalk.white;
5318
+ const riskColor = (_g = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[bridgeCtx.riskLevel]) != null ? _g : chalk.white;
4982
5319
  console.log("");
4983
5320
  console.log(GO_ACCENT.bold("RunTrim go"));
4984
5321
  console.log("");
@@ -5858,6 +6195,49 @@ var statusColors = {
5858
6195
  drift_detected: chalk.red,
5859
6196
  blocked: chalk.red
5860
6197
  };
6198
+ var GLOBAL_USAGE_FILE = path10.join(os3.homedir(), ".runtrim", "usage.json");
6199
+ var FREE_BRIDGE_LIMIT_LOCAL = 5;
6200
+ function currentUsagePeriod() {
6201
+ const d = /* @__PURE__ */ new Date();
6202
+ return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`;
6203
+ }
6204
+ function loadLocalUsageRuns() {
6205
+ var _a2;
6206
+ try {
6207
+ const raw = JSON.parse(fs10.readFileSync(GLOBAL_USAGE_FILE, "utf-8"));
6208
+ return (_a2 = raw.bridgeRuns) != null ? _a2 : {};
6209
+ } catch (e) {
6210
+ return {};
6211
+ }
6212
+ }
6213
+ function saveLocalUsageRuns(bridgeRuns) {
6214
+ const dir = path10.dirname(GLOBAL_USAGE_FILE);
6215
+ if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
6216
+ fs10.writeFileSync(GLOBAL_USAGE_FILE, JSON.stringify({ bridgeRuns }, null, 2), "utf-8");
6217
+ }
6218
+ function checkAndIncrementLocalUsage() {
6219
+ var _a2;
6220
+ const period = currentUsagePeriod();
6221
+ const runs = loadLocalUsageRuns();
6222
+ const used = (_a2 = runs[period]) != null ? _a2 : 0;
6223
+ if (used >= FREE_BRIDGE_LIMIT_LOCAL) {
6224
+ return { allowed: false, used };
6225
+ }
6226
+ runs[period] = used + 1;
6227
+ saveLocalUsageRuns(runs);
6228
+ return { allowed: true, used: used + 1 };
6229
+ }
6230
+ function printBridgeLimitMessage(used) {
6231
+ console.log("");
6232
+ console.log(chalk.red.bold(" Free Bridge limit reached."));
6233
+ console.log("");
6234
+ console.log(chalk.white(` You have used ${used} local guarded run${used === 1 ? "" : "s"} this month.`));
6235
+ console.log(DIM(" Upgrade to Pro for unlimited Bridge Mode, cloud sync, project memory,"));
6236
+ console.log(DIM(" reports, and continuation history."));
6237
+ console.log("");
6238
+ console.log(chalk.white(" https://www.runtrim.com/pricing"));
6239
+ console.log("");
6240
+ }
5861
6241
  var GLOBAL_AUTH_DIR = path10.join(os3.homedir(), ".runtrim");
5862
6242
  var GLOBAL_AUTH_FILE = path10.join(GLOBAL_AUTH_DIR, "auth.json");
5863
6243
  function loadGlobalAuth() {
@@ -6062,6 +6442,14 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
6062
6442
  const freshRuns = loadAllRuns(cwd);
6063
6443
  const updatedRun = (_k = freshRuns.find((r) => r.id === activeRun.id)) != null ? _k : activeRun;
6064
6444
  writeMemoryFromRuns(updatedRun, freshRuns, config, cwd);
6445
+ archiveContract(cwd, activeRun.id);
6446
+ archiveMemory(cwd, activeRun.id);
6447
+ writeCanonicalRuntrimMd(cwd, projectName);
6448
+ writeRestingContract(cwd);
6449
+ writeRestingMemory(cwd);
6450
+ const bridgeRemovals = [];
6451
+ if (removeBridgeBlock(path10.join(cwd, "CLAUDE.md"))) bridgeRemovals.push("CLAUDE.md");
6452
+ if (removeBridgeBlock(path10.join(cwd, "AGENTS.md"))) bridgeRemovals.push("AGENTS.md");
6065
6453
  let synced = false;
6066
6454
  if (options.sync !== false) {
6067
6455
  const globalAuth = loadGlobalAuth();
@@ -6152,6 +6540,16 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
6152
6540
  console.log(DIM(" ") + (synced ? chalk.white("Completed.") : DIM("Skipped. Run runtrim login to connect your dashboard.")));
6153
6541
  console.log("");
6154
6542
  }
6543
+ console.log(GO_ACCENT.bold("Protocol"));
6544
+ console.log(DIM(" ") + chalk.white("RUNTRIM.md restored"));
6545
+ console.log(DIM(" ") + chalk.white("latest contract archived"));
6546
+ console.log(DIM(" ") + chalk.white("current memory reset"));
6547
+ if (bridgeRemovals.length > 0) {
6548
+ for (const f of bridgeRemovals) {
6549
+ console.log(DIM(" ") + chalk.white(`${f} bridge block removed`));
6550
+ }
6551
+ }
6552
+ console.log("");
6155
6553
  });
6156
6554
  program.command("sync").description("Sync local run history and project memory to your RunTrim dashboard").option("--dry-run", "Show what would be synced without uploading").action(async (opts) => {
6157
6555
  var _a2, _b, _c, _d, _e, _f;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runtrim",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "CLI guard layer that scopes AI coding runs before they waste tokens.",
5
5
  "license": "MIT",
6
6
  "author": "RunTrim",