runtrim 0.1.18 → 0.1.20
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/README.md +104 -76
- package/dist-cli/runtrim.cjs +872 -51
- package/dist-cli/runtrim.js +872 -51
- package/package.json +1 -1
package/dist-cli/runtrim.cjs
CHANGED
|
@@ -118,8 +118,45 @@ function getConfigPath(cwd = process.cwd()) {
|
|
|
118
118
|
return import_path.default.join(getConfigDir(cwd), "config.json");
|
|
119
119
|
}
|
|
120
120
|
function getRunsDir(cwd = process.cwd()) {
|
|
121
|
+
return import_path.default.join(getInternalDir(cwd), "runs");
|
|
122
|
+
}
|
|
123
|
+
function getLegacyRunsDir(cwd = process.cwd()) {
|
|
121
124
|
return import_path.default.join(getConfigDir(cwd), "runs");
|
|
122
125
|
}
|
|
126
|
+
function getInternalDir(cwd = process.cwd()) {
|
|
127
|
+
return import_path.default.join(getConfigDir(cwd), "internal");
|
|
128
|
+
}
|
|
129
|
+
function getPreviewsDir(cwd = process.cwd()) {
|
|
130
|
+
return import_path.default.join(getInternalDir(cwd), "previews");
|
|
131
|
+
}
|
|
132
|
+
function getLegacyPreviewsDir(cwd = process.cwd()) {
|
|
133
|
+
return import_path.default.join(getConfigDir(cwd), "previews");
|
|
134
|
+
}
|
|
135
|
+
function getRestoresDir(cwd = process.cwd()) {
|
|
136
|
+
return import_path.default.join(getInternalDir(cwd), "restores");
|
|
137
|
+
}
|
|
138
|
+
function getLegacyRestoresDir(cwd = process.cwd()) {
|
|
139
|
+
return import_path.default.join(getConfigDir(cwd), "restores");
|
|
140
|
+
}
|
|
141
|
+
function getContractsArchiveDir(cwd = process.cwd()) {
|
|
142
|
+
return import_path.default.join(getInternalDir(cwd), "contracts-archive");
|
|
143
|
+
}
|
|
144
|
+
function getAgentArchiveDir(cwd = process.cwd()) {
|
|
145
|
+
return import_path.default.join(getInternalDir(cwd), "agent-archive");
|
|
146
|
+
}
|
|
147
|
+
function ensureInternalArtifactDirs(cwd = process.cwd()) {
|
|
148
|
+
const dirs = [
|
|
149
|
+
getInternalDir(cwd),
|
|
150
|
+
getRunsDir(cwd),
|
|
151
|
+
getPreviewsDir(cwd),
|
|
152
|
+
getRestoresDir(cwd),
|
|
153
|
+
getContractsArchiveDir(cwd),
|
|
154
|
+
getAgentArchiveDir(cwd)
|
|
155
|
+
];
|
|
156
|
+
for (const dir of dirs) {
|
|
157
|
+
if (!import_fs.default.existsSync(dir)) import_fs.default.mkdirSync(dir, { recursive: true });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
123
160
|
function configExists(cwd = process.cwd()) {
|
|
124
161
|
return import_fs.default.existsSync(getConfigPath(cwd));
|
|
125
162
|
}
|
|
@@ -1573,35 +1610,38 @@ function saveRun(task, audit, contract, cwd = process.cwd()) {
|
|
|
1573
1610
|
return record;
|
|
1574
1611
|
}
|
|
1575
1612
|
function loadLatestRun(cwd = process.cwd()) {
|
|
1576
|
-
const
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1613
|
+
const candidateDirs = [getRunsDir(cwd), getLegacyRunsDir(cwd)].filter((dir, idx, arr) => arr.indexOf(dir) === idx);
|
|
1614
|
+
const files = candidateDirs.filter((dir) => import_fs3.default.existsSync(dir)).flatMap(
|
|
1615
|
+
(dir) => import_fs3.default.readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => ({
|
|
1616
|
+
dir,
|
|
1617
|
+
name: f,
|
|
1618
|
+
time: import_fs3.default.statSync(import_path3.default.join(dir, f)).mtime.getTime()
|
|
1619
|
+
}))
|
|
1620
|
+
).sort((a, b) => b.time - a.time);
|
|
1582
1621
|
if (files.length === 0) return null;
|
|
1583
1622
|
try {
|
|
1584
1623
|
return JSON.parse(
|
|
1585
|
-
import_fs3.default.readFileSync(import_path3.default.join(
|
|
1624
|
+
import_fs3.default.readFileSync(import_path3.default.join(files[0].dir, files[0].name), "utf-8")
|
|
1586
1625
|
);
|
|
1587
1626
|
} catch (e) {
|
|
1588
1627
|
return null;
|
|
1589
1628
|
}
|
|
1590
1629
|
}
|
|
1591
1630
|
function updateRun(runId, updates, cwd = process.cwd()) {
|
|
1592
|
-
const
|
|
1631
|
+
const preferredPath = import_path3.default.join(getRunsDir(cwd), `${runId}.json`);
|
|
1632
|
+
const legacyPath = import_path3.default.join(getLegacyRunsDir(cwd), `${runId}.json`);
|
|
1633
|
+
const filePath = import_fs3.default.existsSync(preferredPath) ? preferredPath : legacyPath;
|
|
1593
1634
|
if (!import_fs3.default.existsSync(filePath)) return;
|
|
1594
1635
|
const existing = JSON.parse(import_fs3.default.readFileSync(filePath, "utf-8"));
|
|
1595
1636
|
import_fs3.default.writeFileSync(filePath, JSON.stringify(__spreadValues(__spreadValues({}, existing), updates), null, 2));
|
|
1596
1637
|
}
|
|
1597
1638
|
function loadAllRuns(cwd = process.cwd()) {
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
|
|
1639
|
+
const candidateDirs = [getRunsDir(cwd), getLegacyRunsDir(cwd)].filter((dir, idx, arr) => arr.indexOf(dir) === idx);
|
|
1640
|
+
const files = candidateDirs.filter((dir) => import_fs3.default.existsSync(dir)).flatMap((dir) => import_fs3.default.readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => import_path3.default.join(dir, f)));
|
|
1641
|
+
const deduped = [...new Set(files)];
|
|
1642
|
+
return deduped.map((filePath) => {
|
|
1601
1643
|
try {
|
|
1602
|
-
return JSON.parse(
|
|
1603
|
-
import_fs3.default.readFileSync(import_path3.default.join(runsDir, f), "utf-8")
|
|
1604
|
-
);
|
|
1644
|
+
return JSON.parse(import_fs3.default.readFileSync(filePath, "utf-8"));
|
|
1605
1645
|
} catch (e) {
|
|
1606
1646
|
return null;
|
|
1607
1647
|
}
|
|
@@ -2794,7 +2834,7 @@ function writeCanonicalRuntrimMd(cwd = process.cwd(), projectName) {
|
|
|
2794
2834
|
"6. After editing, tell the user to run: `runtrim finish`",
|
|
2795
2835
|
"",
|
|
2796
2836
|
"---",
|
|
2797
|
-
`Protocol: runtrim
|
|
2837
|
+
`Protocol: runtrim start. Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`
|
|
2798
2838
|
];
|
|
2799
2839
|
import_fs7.default.writeFileSync(import_path7.default.join(cwd, "RUNTRIM.md"), lines.join("\n"), "utf-8");
|
|
2800
2840
|
}
|
|
@@ -2850,7 +2890,7 @@ function archiveContract(cwd, runId) {
|
|
|
2850
2890
|
if (!import_fs7.default.existsSync(latestPath)) return;
|
|
2851
2891
|
const content = import_fs7.default.readFileSync(latestPath, "utf-8");
|
|
2852
2892
|
if (content.includes("Status: none")) return;
|
|
2853
|
-
const archiveDir =
|
|
2893
|
+
const archiveDir = getContractsArchiveDir(cwd);
|
|
2854
2894
|
if (!import_fs7.default.existsSync(archiveDir)) import_fs7.default.mkdirSync(archiveDir, { recursive: true });
|
|
2855
2895
|
import_fs7.default.writeFileSync(import_path7.default.join(archiveDir, `${runId}.md`), content, "utf-8");
|
|
2856
2896
|
}
|
|
@@ -4180,9 +4220,15 @@ async function getPanelState(cwd, monitorMode) {
|
|
|
4180
4220
|
let runs = [];
|
|
4181
4221
|
let latest = null;
|
|
4182
4222
|
let registry = {
|
|
4183
|
-
version:
|
|
4223
|
+
version: 2,
|
|
4224
|
+
stateVersion: 2,
|
|
4184
4225
|
plan: "free",
|
|
4226
|
+
machineInstallId: "",
|
|
4227
|
+
createdAt: "",
|
|
4228
|
+
updatedAt: "",
|
|
4185
4229
|
trackedRepos: [],
|
|
4230
|
+
lastKnownRepo: null,
|
|
4231
|
+
integrity: { algorithm: "sha256-local-seal-v1", seal: "" },
|
|
4186
4232
|
telemetry: {
|
|
4187
4233
|
enabled: false,
|
|
4188
4234
|
anonymousId: ""
|
|
@@ -4224,9 +4270,15 @@ async function getPanelState(cwd, monitorMode) {
|
|
|
4224
4270
|
} catch (e) {
|
|
4225
4271
|
warnings.push("global_registry_failed");
|
|
4226
4272
|
registry = {
|
|
4227
|
-
version:
|
|
4273
|
+
version: 2,
|
|
4274
|
+
stateVersion: 2,
|
|
4228
4275
|
plan: "free",
|
|
4276
|
+
machineInstallId: "",
|
|
4277
|
+
createdAt: "",
|
|
4278
|
+
updatedAt: "",
|
|
4229
4279
|
trackedRepos: [],
|
|
4280
|
+
lastKnownRepo: null,
|
|
4281
|
+
integrity: { algorithm: "sha256-local-seal-v1", seal: "" },
|
|
4230
4282
|
telemetry: {
|
|
4231
4283
|
enabled: false,
|
|
4232
4284
|
anonymousId: ""
|
|
@@ -4694,7 +4746,7 @@ Before editing code:
|
|
|
4694
4746
|
If no active RunTrim contract exists:
|
|
4695
4747
|
- Do not edit code without one.
|
|
4696
4748
|
- Ask the user to start a guarded run:
|
|
4697
|
-
runtrim
|
|
4749
|
+
runtrim agent "<task>" --copy
|
|
4698
4750
|
|
|
4699
4751
|
If the task requires leaving the current scope:
|
|
4700
4752
|
- Stop.
|
|
@@ -4790,7 +4842,7 @@ ${BASE_PROTOCOL}
|
|
|
4790
4842
|
Before using any tool or executing any command:
|
|
4791
4843
|
1. Confirm a RunTrim contract is active at .runtrim/contracts/latest.md.
|
|
4792
4844
|
2. If no active contract exists, do not proceed. Ask for:
|
|
4793
|
-
runtrim
|
|
4845
|
+
runtrim agent "<task>" --copy
|
|
4794
4846
|
3. Do not call shell commands, write files, or read env vars outside the contract.
|
|
4795
4847
|
`.trim(),
|
|
4796
4848
|
custom: `
|
|
@@ -4843,7 +4895,7 @@ function getCursorMdcContent() {
|
|
|
4843
4895
|
"## If no active contract",
|
|
4844
4896
|
"",
|
|
4845
4897
|
"Ask the user to start a guarded run:",
|
|
4846
|
-
'`runtrim
|
|
4898
|
+
'`runtrim agent "<task>" --copy`',
|
|
4847
4899
|
"",
|
|
4848
4900
|
"Any agent. One run boundary."
|
|
4849
4901
|
].join("\n");
|
|
@@ -5105,7 +5157,7 @@ Before editing code:
|
|
|
5105
5157
|
- If the task touches auth, billing, payments, webhooks, database, middleware,
|
|
5106
5158
|
env vars, secrets, or subscriptions, stop and require an active RunTrim contract.
|
|
5107
5159
|
Ask the user to run:
|
|
5108
|
-
runtrim
|
|
5160
|
+
runtrim agent "<task>" --copy
|
|
5109
5161
|
- For low-risk work (UI polish, copy, docs, isolated component styling):
|
|
5110
5162
|
Fast Path is allowed if no unfinished changes exist.
|
|
5111
5163
|
Keep the change minimal.
|
|
@@ -5128,7 +5180,7 @@ No active RunTrim contract means no code edits.
|
|
|
5128
5180
|
If no active contract exists at .runtrim/contracts/latest.md:
|
|
5129
5181
|
- Do not edit any file.
|
|
5130
5182
|
- Ask the user to start a guarded run:
|
|
5131
|
-
runtrim
|
|
5183
|
+
runtrim agent "<task>" --copy
|
|
5132
5184
|
|
|
5133
5185
|
After every editing session:
|
|
5134
5186
|
- Ask the user to run:
|
|
@@ -5142,7 +5194,7 @@ Fast Path is allowed for low and medium risk work.
|
|
|
5142
5194
|
|
|
5143
5195
|
Critical systems (auth, billing, payments, webhooks, database, middleware,
|
|
5144
5196
|
env vars, secrets, subscriptions) still require a RunTrim contract:
|
|
5145
|
-
runtrim
|
|
5197
|
+
runtrim agent "<task>" --copy
|
|
5146
5198
|
|
|
5147
5199
|
After any edits:
|
|
5148
5200
|
- runtrim finish is required before continuing to another task.
|
|
@@ -5153,7 +5205,7 @@ RunTrim Auto-guard: Off
|
|
|
5153
5205
|
|
|
5154
5206
|
Auto-guard is disabled for this project.
|
|
5155
5207
|
RunTrim can still be used manually:
|
|
5156
|
-
runtrim
|
|
5208
|
+
runtrim agent "<task>" --copy
|
|
5157
5209
|
runtrim finish
|
|
5158
5210
|
`.trim();
|
|
5159
5211
|
}
|
|
@@ -5179,7 +5231,7 @@ function saveFastRunRecord(cwd, changedFiles, risk) {
|
|
|
5179
5231
|
reportParts.push(`${sensitive.length} sensitive path${sensitive.length === 1 ? "" : "s"} touched.`);
|
|
5180
5232
|
}
|
|
5181
5233
|
reportParts.push("No pre-run contract was captured for this run.");
|
|
5182
|
-
const nextSafeAction = changedFiles.length > 0 ? 'Create a contract before the next change: runtrim
|
|
5234
|
+
const nextSafeAction = changedFiles.length > 0 ? 'Create a contract before the next change: runtrim agent "<task>" --copy' : 'Start a guarded run: runtrim agent "<task>" --copy';
|
|
5183
5235
|
const summary = {
|
|
5184
5236
|
id,
|
|
5185
5237
|
task,
|
|
@@ -5699,7 +5751,7 @@ function recommendProviderRouting(ctx) {
|
|
|
5699
5751
|
} else if (route === "split-required") {
|
|
5700
5752
|
routingReason = "This spans multiple critical systems, so RunTrim should split into audit, implementation, and verification.";
|
|
5701
5753
|
}
|
|
5702
|
-
let nextCommand = `runtrim
|
|
5754
|
+
let nextCommand = `runtrim agent "${ctx.task}" --copy`;
|
|
5703
5755
|
if (route === "split-required") {
|
|
5704
5756
|
nextCommand = "split into:\n1. audit only\n2. implementation only\n3. verification only";
|
|
5705
5757
|
} else if (route === "preview-only") {
|
|
@@ -5721,6 +5773,7 @@ var _a;
|
|
|
5721
5773
|
var oraFactory = typeof import_ora.default === "function" ? import_ora.default : (_a = import_ora.default.default) != null ? _a : import_ora.default;
|
|
5722
5774
|
var ACCENT = chalk.hex("#C8901A");
|
|
5723
5775
|
var GO_ACCENT = chalk.hex("#8B7CFF");
|
|
5776
|
+
var RUNTRIM_AGENT_INSTRUCTIONS_VERSION = "2";
|
|
5724
5777
|
var DIM = chalk.gray;
|
|
5725
5778
|
var BOLD = chalk.white.bold;
|
|
5726
5779
|
var program = new import_commander.Command();
|
|
@@ -5856,6 +5909,98 @@ async function copyToClipboardSafe(value) {
|
|
|
5856
5909
|
function dedupeFiles(files) {
|
|
5857
5910
|
return [...new Set(files.filter(Boolean).map((f) => f.replace(/\\/g, "/")))];
|
|
5858
5911
|
}
|
|
5912
|
+
var RUNTRIM_GITIGNORE_BLOCK_START = "# BEGIN RUNTRIM_ARTIFACTS";
|
|
5913
|
+
var RUNTRIM_GITIGNORE_BLOCK_END = "# END RUNTRIM_ARTIFACTS";
|
|
5914
|
+
function ensureRuntrimReadme(cwd) {
|
|
5915
|
+
const readmePath = import_path13.default.join(getConfigDir(cwd), "README.md");
|
|
5916
|
+
const content = [
|
|
5917
|
+
"# RunTrim Local Files",
|
|
5918
|
+
"",
|
|
5919
|
+
"RunTrim stores local metadata in this folder.",
|
|
5920
|
+
"",
|
|
5921
|
+
"Human-facing files:",
|
|
5922
|
+
"- `agent/instructions.md` and `agent/latest.md`",
|
|
5923
|
+
"- `contracts/latest.md`",
|
|
5924
|
+
"- `memory/current.md` and `memory/baseline.md`",
|
|
5925
|
+
"- `mcp/*.json`",
|
|
5926
|
+
"- `config.json`",
|
|
5927
|
+
"",
|
|
5928
|
+
"Internal artifacts:",
|
|
5929
|
+
"- `.runtrim/internal/runs/`",
|
|
5930
|
+
"- `.runtrim/internal/previews/`",
|
|
5931
|
+
"- `.runtrim/internal/restores/`",
|
|
5932
|
+
"- `.runtrim/internal/contracts-archive/`",
|
|
5933
|
+
"- `.runtrim/internal/agent-archive/`",
|
|
5934
|
+
"",
|
|
5935
|
+
"Notes:",
|
|
5936
|
+
"- Artifacts are local-first.",
|
|
5937
|
+
"- Source code is not uploaded by local storage.",
|
|
5938
|
+
"- Restore metadata is path-only and does not store secret contents."
|
|
5939
|
+
].join("\n");
|
|
5940
|
+
import_fs13.default.writeFileSync(readmePath, content + "\n", "utf-8");
|
|
5941
|
+
}
|
|
5942
|
+
function ensureRuntrimGitignoreGuidance(cwd) {
|
|
5943
|
+
const gitignorePath = import_path13.default.join(cwd, ".gitignore");
|
|
5944
|
+
if (!import_fs13.default.existsSync(gitignorePath)) return;
|
|
5945
|
+
const desired = [
|
|
5946
|
+
RUNTRIM_GITIGNORE_BLOCK_START,
|
|
5947
|
+
"# RunTrim local artifacts",
|
|
5948
|
+
".runtrim/internal/",
|
|
5949
|
+
".runtrim/runs/",
|
|
5950
|
+
".runtrim/previews/",
|
|
5951
|
+
".runtrim/restores/",
|
|
5952
|
+
".runtrim/contracts/archive/",
|
|
5953
|
+
".runtrim/agent/*.json",
|
|
5954
|
+
RUNTRIM_GITIGNORE_BLOCK_END
|
|
5955
|
+
].join("\n");
|
|
5956
|
+
const current = import_fs13.default.readFileSync(gitignorePath, "utf-8");
|
|
5957
|
+
const start = current.indexOf(RUNTRIM_GITIGNORE_BLOCK_START);
|
|
5958
|
+
const end = current.indexOf(RUNTRIM_GITIGNORE_BLOCK_END);
|
|
5959
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
5960
|
+
const next = current.slice(0, start).trimEnd() + "\n\n" + desired + "\n" + current.slice(end + RUNTRIM_GITIGNORE_BLOCK_END.length).replace(/^\n+/, "\n");
|
|
5961
|
+
if (next !== current) import_fs13.default.writeFileSync(gitignorePath, next, "utf-8");
|
|
5962
|
+
return;
|
|
5963
|
+
}
|
|
5964
|
+
if (!current.includes(".runtrim/internal/")) {
|
|
5965
|
+
import_fs13.default.writeFileSync(gitignorePath, current.trimEnd() + "\n\n" + desired + "\n", "utf-8");
|
|
5966
|
+
}
|
|
5967
|
+
}
|
|
5968
|
+
function listFilesIfExists(dir) {
|
|
5969
|
+
if (!import_fs13.default.existsSync(dir)) return [];
|
|
5970
|
+
return import_fs13.default.readdirSync(dir).map((name) => import_path13.default.join(dir, name)).filter((p) => import_fs13.default.existsSync(p) && import_fs13.default.statSync(p).isFile());
|
|
5971
|
+
}
|
|
5972
|
+
function listArtifactFiles(cwd) {
|
|
5973
|
+
const dirs = [
|
|
5974
|
+
getRunsDir(cwd),
|
|
5975
|
+
getPreviewsDir(cwd),
|
|
5976
|
+
getRestoresDir(cwd),
|
|
5977
|
+
getContractsArchiveDir(cwd),
|
|
5978
|
+
import_path13.default.join(getConfigDir(cwd), "internal", "agent-archive"),
|
|
5979
|
+
getLegacyRunsDir(cwd),
|
|
5980
|
+
getLegacyPreviewsDir(cwd),
|
|
5981
|
+
getLegacyRestoresDir(cwd),
|
|
5982
|
+
import_path13.default.join(getConfigDir(cwd), "contracts", "archive"),
|
|
5983
|
+
import_path13.default.join(getConfigDir(cwd), "agent")
|
|
5984
|
+
];
|
|
5985
|
+
const files = dirs.flatMap((dir) => listFilesIfExists(dir));
|
|
5986
|
+
return files.filter((filePath) => {
|
|
5987
|
+
const base = import_path13.default.basename(filePath).toLowerCase();
|
|
5988
|
+
const rel = import_path13.default.relative(cwd, filePath).replace(/\\/g, "/");
|
|
5989
|
+
if (rel === ".runtrim/previews/latest.md") return false;
|
|
5990
|
+
if (base === "latest.md" || base === "instructions.md" || base === "current.md" || base === "baseline.md") return false;
|
|
5991
|
+
return true;
|
|
5992
|
+
});
|
|
5993
|
+
}
|
|
5994
|
+
function parseRunIdFromArtifact(filePath) {
|
|
5995
|
+
const base = import_path13.default.basename(filePath);
|
|
5996
|
+
const direct = base.match(/^([a-zA-Z0-9_-]{6,})\.json$/);
|
|
5997
|
+
if (direct) return direct[1];
|
|
5998
|
+
const report = base.match(/^([a-zA-Z0-9_-]{6,})\.report\.\d+\.json$/);
|
|
5999
|
+
if (report) return report[1];
|
|
6000
|
+
const out = base.match(/^([a-zA-Z0-9_-]{6,})\.output\.txt$/);
|
|
6001
|
+
if (out) return out[1];
|
|
6002
|
+
return null;
|
|
6003
|
+
}
|
|
5859
6004
|
function normalizeContractPathPattern(pattern) {
|
|
5860
6005
|
let p = pattern.trim().replace(/\\/g, "/");
|
|
5861
6006
|
if (!p || p === "-" || p.toLowerCase() === "none") return "";
|
|
@@ -6025,10 +6170,11 @@ function buildRecommendedNextCommand(task, approval, filesToInspect) {
|
|
|
6025
6170
|
return `runtrim go "${task}"`;
|
|
6026
6171
|
}
|
|
6027
6172
|
function writePreviewArtifacts(cwd, preview) {
|
|
6028
|
-
const previewsDir =
|
|
6173
|
+
const previewsDir = getPreviewsDir(cwd);
|
|
6029
6174
|
if (!import_fs13.default.existsSync(previewsDir)) import_fs13.default.mkdirSync(previewsDir, { recursive: true });
|
|
6030
6175
|
const jsonPath = import_path13.default.join(previewsDir, `${preview.id}.json`);
|
|
6031
|
-
const markdownPath = import_path13.default.join(
|
|
6176
|
+
const markdownPath = import_path13.default.join(getLegacyPreviewsDir(cwd), "latest.md");
|
|
6177
|
+
if (!import_fs13.default.existsSync(import_path13.default.dirname(markdownPath))) import_fs13.default.mkdirSync(import_path13.default.dirname(markdownPath), { recursive: true });
|
|
6032
6178
|
import_fs13.default.writeFileSync(jsonPath, JSON.stringify(preview, null, 2), "utf-8");
|
|
6033
6179
|
const lines = [
|
|
6034
6180
|
"RunTrim Agent Preview",
|
|
@@ -6067,7 +6213,7 @@ function writePreviewArtifacts(cwd, preview) {
|
|
|
6067
6213
|
"Next:",
|
|
6068
6214
|
preview.recommendedNextCommand,
|
|
6069
6215
|
"",
|
|
6070
|
-
`Preview JSON: .runtrim/previews/${preview.id}.json`
|
|
6216
|
+
`Preview JSON: .runtrim/internal/previews/${preview.id}.json`
|
|
6071
6217
|
];
|
|
6072
6218
|
import_fs13.default.writeFileSync(markdownPath, lines.join("\n"), "utf-8");
|
|
6073
6219
|
return { jsonPath, markdownPath };
|
|
@@ -6449,6 +6595,7 @@ async function runAgentApply(task, mode) {
|
|
|
6449
6595
|
const copied = mode.copy ? await copyToClipboardSafe(import_fs13.default.readFileSync(handoff.markdownPath, "utf-8")) : false;
|
|
6450
6596
|
const run = saveRun(task, previewResult.audit, previewResult.contract, cwd);
|
|
6451
6597
|
updateRun(run.id, { status: "guarded" }, cwd);
|
|
6598
|
+
await captureRestorePoint(cwd, run.id, task);
|
|
6452
6599
|
const riskColor = (_a2 = { low: chalk.green, medium: chalk.yellow, high: chalk.hex("#FF8C00"), critical: chalk.red }[risk]) != null ? _a2 : chalk.white;
|
|
6453
6600
|
if (mode.primary) {
|
|
6454
6601
|
console.log("");
|
|
@@ -6635,6 +6782,7 @@ async function runControlledExecution(task, mode) {
|
|
|
6635
6782
|
controlledExecutionId: id,
|
|
6636
6783
|
controlledExecutionStatus: status
|
|
6637
6784
|
}, cwd);
|
|
6785
|
+
await captureRestorePoint(cwd, run.id, task);
|
|
6638
6786
|
}
|
|
6639
6787
|
if (status === "blocked") {
|
|
6640
6788
|
console.log("");
|
|
@@ -6925,14 +7073,18 @@ ${block}
|
|
|
6925
7073
|
function buildRuntrimAgentBlockContent() {
|
|
6926
7074
|
return [
|
|
6927
7075
|
"RunTrim project instructions",
|
|
7076
|
+
`RUNTRIM_AGENT_INSTRUCTIONS_VERSION: ${RUNTRIM_AGENT_INSTRUCTIONS_VERSION}`,
|
|
6928
7077
|
"- Before editing, read RunTrim project memory and profile context.",
|
|
6929
|
-
"-
|
|
7078
|
+
"- Before editing, create or verify an active RunTrim contract for the current user task.",
|
|
7079
|
+
"- If MCP is available and no active contract exists, call runtrim_create_contract with the user's task.",
|
|
6930
7080
|
'- If MCP is unavailable, ask the user to run: runtrim agent "task" --copy',
|
|
6931
|
-
"-
|
|
6932
|
-
"-
|
|
7081
|
+
"- Before touching risky paths (auth, billing, middleware/proxy, migrations, env/sensitive files, broad app-wide refactors), call runtrim_check_path when available.",
|
|
7082
|
+
"- Use RunTrim project memory and stay inside active contract scope.",
|
|
7083
|
+
"- Do not silently expand scope. Preserve existing project style and logic.",
|
|
6933
7084
|
"- Never read or print env file contents.",
|
|
6934
|
-
'-
|
|
6935
|
-
"- At the end, run
|
|
7085
|
+
'- If scope must expand, stop and request approval: runtrim approve "Allow <path/scope> for this run only"',
|
|
7086
|
+
"- At the end, run runtrim finish if terminal execution is available, otherwise ask the user to run runtrim finish.",
|
|
7087
|
+
"- Do not claim completion before finish verification when RunTrim is active.",
|
|
6936
7088
|
"",
|
|
6937
7089
|
"RunTrim MCP:",
|
|
6938
7090
|
"- runtrim mcp instructions",
|
|
@@ -7092,12 +7244,16 @@ function updateStartAgentInstructions(cwd) {
|
|
|
7092
7244
|
const existing = import_fs13.default.existsSync(instructionsPath) ? import_fs13.default.readFileSync(instructionsPath, "utf-8") : "";
|
|
7093
7245
|
const body = [
|
|
7094
7246
|
"RunTrim agent instructions:",
|
|
7247
|
+
`RUNTRIM_AGENT_INSTRUCTIONS_VERSION: ${RUNTRIM_AGENT_INSTRUCTIONS_VERSION}`,
|
|
7095
7248
|
"- Use RunTrim project memory and profile before editing.",
|
|
7096
|
-
"-
|
|
7249
|
+
"- Create or verify an active RunTrim contract before edits.",
|
|
7250
|
+
"- If MCP is available and contract is missing, call runtrim_create_contract with the user's task.",
|
|
7097
7251
|
'- If MCP is unavailable, ask user to run: runtrim agent "task" --copy',
|
|
7098
|
-
"- For high-risk paths (auth, billing, middleware/proxy, migrations, sensitive files, broad app-wide changes), check scope first. If MCP is available, call runtrim_check_path.",
|
|
7252
|
+
"- For high-risk paths (auth, billing, middleware/proxy, migrations, env/sensitive files, broad app-wide changes), check scope first. If MCP is available, call runtrim_check_path.",
|
|
7253
|
+
"- Stay inside active contract scope and preserve existing project style and logic.",
|
|
7099
7254
|
'- If scope must expand, request: runtrim approve "Allow <path/scope> for this run only"',
|
|
7100
|
-
"- Run finish
|
|
7255
|
+
"- Run runtrim finish when terminal execution is available, otherwise ask the user to run runtrim finish.",
|
|
7256
|
+
"- Do not claim completion before runtrim finish verification when RunTrim is active.",
|
|
7101
7257
|
"- Never read or print env file contents.",
|
|
7102
7258
|
"",
|
|
7103
7259
|
"RunTrim MCP:",
|
|
@@ -7170,6 +7326,202 @@ function detectKnownMcpConfigPresence() {
|
|
|
7170
7326
|
cursorConfigFound: Boolean(cursorMatch)
|
|
7171
7327
|
};
|
|
7172
7328
|
}
|
|
7329
|
+
function getRestorePointPath(cwd, runId) {
|
|
7330
|
+
return import_path13.default.join(getRestoresDir(cwd), `${runId}.json`);
|
|
7331
|
+
}
|
|
7332
|
+
function getLegacyRestorePointPath(cwd, runId) {
|
|
7333
|
+
return import_path13.default.join(getLegacyRestoresDir(cwd), `${runId}.json`);
|
|
7334
|
+
}
|
|
7335
|
+
async function isGitRepo(cwd) {
|
|
7336
|
+
try {
|
|
7337
|
+
await (0, import_execa3.execa)("git", ["rev-parse", "--is-inside-work-tree"], { cwd });
|
|
7338
|
+
return true;
|
|
7339
|
+
} catch (e) {
|
|
7340
|
+
return false;
|
|
7341
|
+
}
|
|
7342
|
+
}
|
|
7343
|
+
async function captureRestorePoint(cwd, runId, task) {
|
|
7344
|
+
const dir = getRestoresDir(cwd);
|
|
7345
|
+
if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
|
|
7346
|
+
const existingPath = getRestorePointPath(cwd, runId);
|
|
7347
|
+
if (import_fs13.default.existsSync(existingPath) || import_fs13.default.existsSync(getLegacyRestorePointPath(cwd, runId))) return;
|
|
7348
|
+
let commit = null;
|
|
7349
|
+
let changedBeforeRun = [];
|
|
7350
|
+
if (await isGitRepo(cwd)) {
|
|
7351
|
+
try {
|
|
7352
|
+
const { stdout } = await (0, import_execa3.execa)("git", ["rev-parse", "HEAD"], { cwd });
|
|
7353
|
+
commit = stdout.trim() || null;
|
|
7354
|
+
} catch (e) {
|
|
7355
|
+
commit = null;
|
|
7356
|
+
}
|
|
7357
|
+
try {
|
|
7358
|
+
const changed = await getGitChangedFiles(cwd);
|
|
7359
|
+
changedBeforeRun = dedupeFiles(changed.map((c) => c.path));
|
|
7360
|
+
} catch (e) {
|
|
7361
|
+
changedBeforeRun = [];
|
|
7362
|
+
}
|
|
7363
|
+
}
|
|
7364
|
+
const record = {
|
|
7365
|
+
runId,
|
|
7366
|
+
task,
|
|
7367
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7368
|
+
preRun: {
|
|
7369
|
+
commit,
|
|
7370
|
+
dirty: changedBeforeRun.length > 0,
|
|
7371
|
+
changedBeforeRun
|
|
7372
|
+
}
|
|
7373
|
+
};
|
|
7374
|
+
import_fs13.default.writeFileSync(existingPath, JSON.stringify(record, null, 2), "utf-8");
|
|
7375
|
+
}
|
|
7376
|
+
function loadRestorePoint(cwd, runId) {
|
|
7377
|
+
const preferred = getRestorePointPath(cwd, runId);
|
|
7378
|
+
const legacy = getLegacyRestorePointPath(cwd, runId);
|
|
7379
|
+
const p = import_fs13.default.existsSync(preferred) ? preferred : legacy;
|
|
7380
|
+
if (!import_fs13.default.existsSync(p)) return null;
|
|
7381
|
+
try {
|
|
7382
|
+
return JSON.parse(import_fs13.default.readFileSync(p, "utf-8"));
|
|
7383
|
+
} catch (e) {
|
|
7384
|
+
return null;
|
|
7385
|
+
}
|
|
7386
|
+
}
|
|
7387
|
+
function saveRestorePoint(cwd, record) {
|
|
7388
|
+
const dir = getRestoresDir(cwd);
|
|
7389
|
+
if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
|
|
7390
|
+
import_fs13.default.writeFileSync(getRestorePointPath(cwd, record.runId), JSON.stringify(record, null, 2), "utf-8");
|
|
7391
|
+
}
|
|
7392
|
+
function isSecretLikePath(file) {
|
|
7393
|
+
const n = file.replace(/\\/g, "/").toLowerCase();
|
|
7394
|
+
return n.includes(".env") || n.endsWith(".env") || n.endsWith(".pem") || n.endsWith(".key") || n.includes("id_rsa") || n.includes("id_ed25519") || n.includes("private-key");
|
|
7395
|
+
}
|
|
7396
|
+
function isDocsLikePath(file) {
|
|
7397
|
+
const n = file.replace(/\\/g, "/").toLowerCase();
|
|
7398
|
+
return n === "readme.md" || n.startsWith("docs/") || n.endsWith(".md") || n.endsWith(".mdx") || n.includes("changelog");
|
|
7399
|
+
}
|
|
7400
|
+
function isUiCheckoutPath(file) {
|
|
7401
|
+
const n = file.replace(/\\/g, "/").toLowerCase();
|
|
7402
|
+
return n.includes("/checkout/") && !n.includes("/api/") && !n.includes("webhook") && !n.includes("stripe") && !n.includes("dodo") && !n.includes("provider") && !n.includes("session");
|
|
7403
|
+
}
|
|
7404
|
+
function isHighRiskLogicPath(file) {
|
|
7405
|
+
const n = file.replace(/\\/g, "/").toLowerCase();
|
|
7406
|
+
if (isSecretLikePath(n)) return true;
|
|
7407
|
+
if (n.endsWith("middleware.ts") || n.endsWith("middleware.js") || n.endsWith("proxy.ts") || n.endsWith("proxy.js")) return true;
|
|
7408
|
+
if (n.includes("supabase/migrations") || n.includes("prisma/migrations") || n.includes("/migrations/")) return true;
|
|
7409
|
+
if (n.includes("/auth/") || n.includes("session") || n.includes("jwt")) return true;
|
|
7410
|
+
if (n.includes("webhook")) return true;
|
|
7411
|
+
if ((n.includes("billing") || n.includes("payment") || n.includes("stripe") || n.includes("dodo")) && (n.includes("/api/") || n.includes("/lib/") || n.includes("route.ts") || n.includes("route.js") || n.includes("server"))) return true;
|
|
7412
|
+
if (n.includes("/checkout/") && !isUiCheckoutPath(n) && (n.includes("/api/") || n.includes("provider") || n.includes("session") || n.includes("route.ts") || n.includes("route.js"))) return true;
|
|
7413
|
+
return false;
|
|
7414
|
+
}
|
|
7415
|
+
function matchesAnyContractRule(file, rules) {
|
|
7416
|
+
return rules.some((rule) => matchesContractPattern(file, rule));
|
|
7417
|
+
}
|
|
7418
|
+
async function detectCiChangedFiles(cwd, base, head) {
|
|
7419
|
+
var _a2;
|
|
7420
|
+
const warnings = [];
|
|
7421
|
+
let baseUsed = (base == null ? void 0 : base.trim()) || "";
|
|
7422
|
+
let headUsed = (head == null ? void 0 : head.trim()) || "";
|
|
7423
|
+
if (!headUsed) {
|
|
7424
|
+
try {
|
|
7425
|
+
const { stdout } = await (0, import_execa3.execa)("git", ["rev-parse", "HEAD"], { cwd });
|
|
7426
|
+
headUsed = stdout.trim();
|
|
7427
|
+
} catch (e) {
|
|
7428
|
+
headUsed = "HEAD";
|
|
7429
|
+
}
|
|
7430
|
+
}
|
|
7431
|
+
if (!baseUsed) {
|
|
7432
|
+
const ghBase = (_a2 = process.env.GITHUB_BASE_REF) == null ? void 0 : _a2.trim();
|
|
7433
|
+
if (ghBase) {
|
|
7434
|
+
baseUsed = `origin/${ghBase}`;
|
|
7435
|
+
}
|
|
7436
|
+
}
|
|
7437
|
+
if (!baseUsed) {
|
|
7438
|
+
for (const candidate of ["origin/main", "origin/master", "main", "master", "HEAD~1"]) {
|
|
7439
|
+
try {
|
|
7440
|
+
await (0, import_execa3.execa)("git", ["rev-parse", "--verify", candidate], { cwd });
|
|
7441
|
+
baseUsed = candidate;
|
|
7442
|
+
break;
|
|
7443
|
+
} catch (e) {
|
|
7444
|
+
continue;
|
|
7445
|
+
}
|
|
7446
|
+
}
|
|
7447
|
+
}
|
|
7448
|
+
if (!baseUsed) {
|
|
7449
|
+
warnings.push("Could not infer a base ref. Using working tree changes only.");
|
|
7450
|
+
}
|
|
7451
|
+
let diffFiles = [];
|
|
7452
|
+
if (baseUsed) {
|
|
7453
|
+
try {
|
|
7454
|
+
const { stdout } = await (0, import_execa3.execa)("git", ["diff", "--name-only", `${baseUsed}...${headUsed}`], { cwd });
|
|
7455
|
+
diffFiles = stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
7456
|
+
} catch (e) {
|
|
7457
|
+
warnings.push(`Could not diff ${baseUsed}...${headUsed}. Falling back to local changes.`);
|
|
7458
|
+
}
|
|
7459
|
+
}
|
|
7460
|
+
if (diffFiles.length === 0) {
|
|
7461
|
+
try {
|
|
7462
|
+
const { stdout } = await (0, import_execa3.execa)("git", ["diff", "--name-only"], { cwd });
|
|
7463
|
+
diffFiles = stdout.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
7464
|
+
} catch (e) {
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
try {
|
|
7468
|
+
const { stdout } = await (0, import_execa3.execa)("git", ["status", "--porcelain"], { cwd });
|
|
7469
|
+
const untracked = stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith("?? ")).map((line) => line.slice(3).trim());
|
|
7470
|
+
diffFiles = dedupeFiles([...diffFiles, ...untracked]);
|
|
7471
|
+
} catch (e) {
|
|
7472
|
+
}
|
|
7473
|
+
return {
|
|
7474
|
+
files: dedupeFiles(diffFiles.map((f) => f.replace(/\\/g, "/"))),
|
|
7475
|
+
baseUsed: baseUsed || "(local)",
|
|
7476
|
+
headUsed,
|
|
7477
|
+
warnings
|
|
7478
|
+
};
|
|
7479
|
+
}
|
|
7480
|
+
function detectMcpConfigState(configPath, found) {
|
|
7481
|
+
if (!found || !configPath) return "not found";
|
|
7482
|
+
try {
|
|
7483
|
+
const raw = JSON.parse(import_fs13.default.readFileSync(configPath, "utf-8"));
|
|
7484
|
+
const servers = raw.mcpServers && typeof raw.mcpServers === "object" ? raw.mcpServers : {};
|
|
7485
|
+
return servers.runtrim ? "configured" : "missing runtrim";
|
|
7486
|
+
} catch (e) {
|
|
7487
|
+
return "missing runtrim";
|
|
7488
|
+
}
|
|
7489
|
+
}
|
|
7490
|
+
function hasCurrentRuntrimBlock(filePath) {
|
|
7491
|
+
if (!import_fs13.default.existsSync(filePath)) return { exists: false, current: false };
|
|
7492
|
+
const content = import_fs13.default.readFileSync(filePath, "utf-8");
|
|
7493
|
+
const hasBlock = content.includes("<!-- RUNTRIM:START -->") && content.includes("<!-- RUNTRIM:END -->");
|
|
7494
|
+
const hasVersion = content.includes(`RUNTRIM_AGENT_INSTRUCTIONS_VERSION: ${RUNTRIM_AGENT_INSTRUCTIONS_VERSION}`);
|
|
7495
|
+
return { exists: hasBlock, current: hasBlock && hasVersion };
|
|
7496
|
+
}
|
|
7497
|
+
function readMcpLastUsed(cwd) {
|
|
7498
|
+
const p = import_path13.default.join(getProjectMcpDir(cwd), "last-used.json");
|
|
7499
|
+
if (!import_fs13.default.existsSync(p)) return { tracked: false, tool: null, usedAt: null };
|
|
7500
|
+
try {
|
|
7501
|
+
const parsed = JSON.parse(import_fs13.default.readFileSync(p, "utf-8"));
|
|
7502
|
+
return {
|
|
7503
|
+
tracked: true,
|
|
7504
|
+
tool: typeof parsed.tool === "string" ? parsed.tool : null,
|
|
7505
|
+
usedAt: typeof parsed.usedAt === "string" ? parsed.usedAt : null
|
|
7506
|
+
};
|
|
7507
|
+
} catch (e) {
|
|
7508
|
+
return { tracked: false, tool: null, usedAt: null };
|
|
7509
|
+
}
|
|
7510
|
+
}
|
|
7511
|
+
function writeMcpLastUsed(cwd, tool) {
|
|
7512
|
+
try {
|
|
7513
|
+
const dir = getProjectMcpDir(cwd);
|
|
7514
|
+
if (!import_fs13.default.existsSync(dir)) import_fs13.default.mkdirSync(dir, { recursive: true });
|
|
7515
|
+
const payload = {
|
|
7516
|
+
tool,
|
|
7517
|
+
usedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7518
|
+
projectPath: cwd
|
|
7519
|
+
};
|
|
7520
|
+
import_fs13.default.writeFileSync(import_path13.default.join(dir, "last-used.json"), `${JSON.stringify(payload, null, 2)}
|
|
7521
|
+
`, "utf-8");
|
|
7522
|
+
} catch (e) {
|
|
7523
|
+
}
|
|
7524
|
+
}
|
|
7173
7525
|
function appendContractAmendment(cwd, approvalText) {
|
|
7174
7526
|
const p = import_path13.default.join(cwd, ".runtrim", "contracts", "latest.md");
|
|
7175
7527
|
if (!import_fs13.default.existsSync(p)) return { ok: false, reason: "missing_contract" };
|
|
@@ -7542,6 +7894,7 @@ async function buildRuntrimCreateContractMcp(cwd, args) {
|
|
|
7542
7894
|
const handoff = writeAgentHandoffArtifacts(cwd, apply, import_path13.default.relative(cwd, previewPath));
|
|
7543
7895
|
const run = saveRun(mergedTask, previewResult.audit, previewResult.contract, cwd);
|
|
7544
7896
|
updateRun(run.id, { status: "guarded" }, cwd);
|
|
7897
|
+
await captureRestorePoint(cwd, run.id, mergedTask);
|
|
7545
7898
|
const payload = {
|
|
7546
7899
|
contract_created: true,
|
|
7547
7900
|
task: taskRaw,
|
|
@@ -7736,6 +8089,7 @@ async function startMcpServerStdio(cwd) {
|
|
|
7736
8089
|
});
|
|
7737
8090
|
return;
|
|
7738
8091
|
}
|
|
8092
|
+
writeMcpLastUsed(cwd, name);
|
|
7739
8093
|
send({
|
|
7740
8094
|
jsonrpc: "2.0",
|
|
7741
8095
|
id,
|
|
@@ -8223,6 +8577,7 @@ async function initializeRunTrim(cwd, options = {}) {
|
|
|
8223
8577
|
const runsDir = getRunsDir(cwd);
|
|
8224
8578
|
if (!import_fs13.default.existsSync(configDir)) import_fs13.default.mkdirSync(configDir, { recursive: true });
|
|
8225
8579
|
if (!import_fs13.default.existsSync(runsDir)) import_fs13.default.mkdirSync(runsDir, { recursive: true });
|
|
8580
|
+
ensureInternalArtifactDirs(cwd);
|
|
8226
8581
|
const existingConfig = hadConfig ? loadConfig(cwd) : null;
|
|
8227
8582
|
const baseConfig = __spreadValues(__spreadValues({}, DEFAULT_CONFIG), detectProjectInfo(cwd));
|
|
8228
8583
|
const nextConfig = options.refresh && existingConfig ? __spreadValues({}, existingConfig) : baseConfig;
|
|
@@ -8242,13 +8597,8 @@ async function initializeRunTrim(cwd, options = {}) {
|
|
|
8242
8597
|
import_fs13.default.writeFileSync(memoryPath, buildBaselineMemoryMarkdown(baseline), "utf-8");
|
|
8243
8598
|
}
|
|
8244
8599
|
ensureStarterPromptIfMissing(cwd);
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
const content = import_fs13.default.readFileSync(gitignorePath, "utf-8");
|
|
8248
|
-
if (!content.includes(".runtrim/runs")) {
|
|
8249
|
-
import_fs13.default.appendFileSync(gitignorePath, "\n# RunTrim run history\n.runtrim/runs/\n");
|
|
8250
|
-
}
|
|
8251
|
-
}
|
|
8600
|
+
ensureRuntrimReadme(cwd);
|
|
8601
|
+
ensureRuntrimGitignoreGuidance(cwd);
|
|
8252
8602
|
return { ok: true };
|
|
8253
8603
|
}
|
|
8254
8604
|
async function runPrepareTask(task, options) {
|
|
@@ -8283,7 +8633,7 @@ async function runPrepareTask(task, options) {
|
|
|
8283
8633
|
console.log("");
|
|
8284
8634
|
console.log(DIM(" Task ") + chalk.white(truncate(task, 70)));
|
|
8285
8635
|
console.log(DIM(" Prompt ") + chalk.white(promptPath2));
|
|
8286
|
-
console.log(DIM(" Run saved ") + chalk.white(`.runtrim/runs/${run.id}.json`));
|
|
8636
|
+
console.log(DIM(" Run saved ") + chalk.white(`.runtrim/internal/runs/${run.id}.json`));
|
|
8287
8637
|
console.log("");
|
|
8288
8638
|
printPrepareAgentInstructions(selectedAgent, config.lastPromptPath);
|
|
8289
8639
|
console.log("");
|
|
@@ -8303,6 +8653,7 @@ async function runPrepareTask(task, options) {
|
|
|
8303
8653
|
const promptPath = writeLatestPromptFile(contract.contractText, config, cwd);
|
|
8304
8654
|
if (options.copy !== false) await copyToClipboardSafe(contract.contractText);
|
|
8305
8655
|
updateRun(run.id, { status: "guarded" }, cwd);
|
|
8656
|
+
await captureRestorePoint(cwd, run.id, task);
|
|
8306
8657
|
const riskColors = {
|
|
8307
8658
|
low: chalk.green,
|
|
8308
8659
|
medium: chalk.yellow,
|
|
@@ -8325,7 +8676,7 @@ async function runPrepareTask(task, options) {
|
|
|
8325
8676
|
);
|
|
8326
8677
|
console.log(DIM(" Reduction ") + chalk.white(contract.riskReductionPercent + "%"));
|
|
8327
8678
|
console.log(DIM(" Prompt ") + chalk.white(promptPath));
|
|
8328
|
-
console.log(DIM(" Run saved ") + chalk.white(`.runtrim/runs/${run.id}.json`));
|
|
8679
|
+
console.log(DIM(" Run saved ") + chalk.white(`.runtrim/internal/runs/${run.id}.json`));
|
|
8329
8680
|
console.log("");
|
|
8330
8681
|
printPrepareAgentInstructions(selectedAgent, config.lastPromptPath);
|
|
8331
8682
|
console.log("");
|
|
@@ -8469,6 +8820,9 @@ program.command("start").description("Guided RunTrim onboarding and daily loop")
|
|
|
8469
8820
|
console.log(chalk.white(" runtrim mcp config --print"));
|
|
8470
8821
|
console.log(chalk.white(" runtrim mcp start"));
|
|
8471
8822
|
console.log("");
|
|
8823
|
+
console.log(DIM(" Check readiness"));
|
|
8824
|
+
console.log(chalk.white(" runtrim doctor"));
|
|
8825
|
+
console.log("");
|
|
8472
8826
|
console.log(DIM(" Adapters"));
|
|
8473
8827
|
console.log(chalk.white(" runtrim adapters"));
|
|
8474
8828
|
console.log(chalk.white(" runtrim adapters status"));
|
|
@@ -8550,6 +8904,108 @@ program.command("start").description("Guided RunTrim onboarding and daily loop")
|
|
|
8550
8904
|
console.log("");
|
|
8551
8905
|
}
|
|
8552
8906
|
});
|
|
8907
|
+
program.command("doctor").description("Check whether the current project is RunTrim-ready for agents, MCP, memory and finish verification").action(async () => {
|
|
8908
|
+
const cwd = process.cwd();
|
|
8909
|
+
const repoCheck = await assertFreeRepoAllowed(cwd);
|
|
8910
|
+
const profilePath = import_path13.default.join(getConfigDir(cwd), "project-profile.json");
|
|
8911
|
+
const memoryPath = import_path13.default.join(getConfigDir(cwd), "memory", "current.md");
|
|
8912
|
+
const instructionsPath = import_path13.default.join(getConfigDir(cwd), "agent", "instructions.md");
|
|
8913
|
+
const snippetsDir = getProjectMcpDir(cwd);
|
|
8914
|
+
const snippetFiles = [
|
|
8915
|
+
import_path13.default.join(snippetsDir, "claude-desktop.json"),
|
|
8916
|
+
import_path13.default.join(snippetsDir, "cursor.json"),
|
|
8917
|
+
import_path13.default.join(snippetsDir, "generic.json")
|
|
8918
|
+
];
|
|
8919
|
+
const profileReady = import_fs13.default.existsSync(profilePath);
|
|
8920
|
+
const memoryReady = import_fs13.default.existsSync(memoryPath) && import_fs13.default.readFileSync(memoryPath, "utf-8").trim().length > 0;
|
|
8921
|
+
const instructionsReady = import_fs13.default.existsSync(instructionsPath) && import_fs13.default.readFileSync(instructionsPath, "utf-8").trim().length > 0;
|
|
8922
|
+
const claudeBlock = hasCurrentRuntrimBlock(import_path13.default.join(cwd, "CLAUDE.md"));
|
|
8923
|
+
const agentsBlock = hasCurrentRuntrimBlock(import_path13.default.join(cwd, "AGENTS.md"));
|
|
8924
|
+
const cursorRulePath = import_path13.default.join(cwd, ".cursor", "rules", "runtrim.mdc");
|
|
8925
|
+
const cursorRuleExists = import_fs13.default.existsSync(cursorRulePath);
|
|
8926
|
+
const cursorRuleCurrent = cursorRuleExists ? import_fs13.default.readFileSync(cursorRulePath, "utf-8").includes(`RUNTRIM_AGENT_INSTRUCTIONS_VERSION: ${RUNTRIM_AGENT_INSTRUCTIONS_VERSION}`) : false;
|
|
8927
|
+
const anySurfaceInstalled = claudeBlock.exists || agentsBlock.exists || cursorRuleExists;
|
|
8928
|
+
const runtrimBlockCurrent = [claudeBlock.current, agentsBlock.current, cursorRuleCurrent].some(Boolean);
|
|
8929
|
+
const snippetsGenerated = snippetFiles.every((p) => import_fs13.default.existsSync(p));
|
|
8930
|
+
const knownMcp = detectKnownMcpConfigPresence();
|
|
8931
|
+
const claudeMcpState = detectMcpConfigState(knownMcp.claudeConfigPath, knownMcp.claudeConfigFound);
|
|
8932
|
+
const cursorMcpState = detectMcpConfigState(knownMcp.cursorConfigPath, knownMcp.cursorConfigFound);
|
|
8933
|
+
const genericReady = snippetsGenerated ? "ready" : "missing";
|
|
8934
|
+
const tools = buildMcpTools();
|
|
8935
|
+
const hasContractTool = tools.some((t) => t.name === "runtrim_create_contract");
|
|
8936
|
+
const hasPathTool = tools.some((t) => t.name === "runtrim_check_path");
|
|
8937
|
+
const hasApprovalTool = tools.some((t) => t.name === "runtrim_suggest_approval");
|
|
8938
|
+
const hasFinishTool = tools.some((t) => t.name === "runtrim_finish_guidance");
|
|
8939
|
+
const contract = parseContractSummary(cwd);
|
|
8940
|
+
const lastMcp = readMcpLastUsed(cwd);
|
|
8941
|
+
const setupCorrupt = configExists(cwd) && (!profileReady || !memoryReady || !instructionsReady);
|
|
8942
|
+
const artifactFiles = listArtifactFiles(cwd);
|
|
8943
|
+
const artifactCount = artifactFiles.length;
|
|
8944
|
+
let readiness = "partial";
|
|
8945
|
+
if (!repoCheck.allowed || setupCorrupt) {
|
|
8946
|
+
readiness = "blocked";
|
|
8947
|
+
} else if (profileReady && memoryReady && instructionsReady && anySurfaceInstalled && snippetsGenerated && hasContractTool && hasPathTool && hasApprovalTool && hasFinishTool) {
|
|
8948
|
+
readiness = "ready";
|
|
8949
|
+
}
|
|
8950
|
+
console.log("");
|
|
8951
|
+
console.log(BOLD("RunTrim") + DIM(" doctor"));
|
|
8952
|
+
console.log("");
|
|
8953
|
+
console.log(BOLD("Project"));
|
|
8954
|
+
console.log(chalk.white(`- Project profile: ${profileReady ? "ready" : "missing"}`));
|
|
8955
|
+
console.log(chalk.white(`- Project memory: ${memoryReady ? "ready" : "missing"}`));
|
|
8956
|
+
console.log(chalk.white(`- Agent instructions: ${instructionsReady ? "ready" : "missing"}`));
|
|
8957
|
+
console.log(chalk.white(`- Active contract: ${contract.active ? "active" : "none"}`));
|
|
8958
|
+
console.log("");
|
|
8959
|
+
console.log(BOLD("Agent rules"));
|
|
8960
|
+
console.log(chalk.white(`- CLAUDE.md: ${claudeBlock.exists ? "installed" : "not found"}`));
|
|
8961
|
+
console.log(chalk.white(`- AGENTS.md: ${agentsBlock.exists ? "installed" : "not found"}`));
|
|
8962
|
+
console.log(chalk.white(`- Cursor rules: ${cursorRuleExists ? "installed" : "not found"}`));
|
|
8963
|
+
console.log(chalk.white(`- RunTrim instruction block: ${runtrimBlockCurrent ? "current" : anySurfaceInstalled ? "stale" : "missing"}`));
|
|
8964
|
+
console.log("");
|
|
8965
|
+
console.log(BOLD("MCP"));
|
|
8966
|
+
console.log(chalk.white("- MCP server: available"));
|
|
8967
|
+
console.log(chalk.white(`- Project snippets: ${snippetsGenerated ? "generated" : "missing"}`));
|
|
8968
|
+
console.log(chalk.white(`- Claude Desktop config: ${claudeMcpState}`));
|
|
8969
|
+
console.log(chalk.white(`- Cursor MCP config: ${cursorMcpState}`));
|
|
8970
|
+
console.log(chalk.white(`- Generic config: ${genericReady}`));
|
|
8971
|
+
if (lastMcp.tracked && lastMcp.tool && lastMcp.usedAt) {
|
|
8972
|
+
console.log(chalk.white(`- Last MCP tool call: ${lastMcp.tool}, ${lastMcp.usedAt}`));
|
|
8973
|
+
} else {
|
|
8974
|
+
console.log(chalk.white("- Last MCP tool call: not tracked yet"));
|
|
8975
|
+
}
|
|
8976
|
+
console.log("");
|
|
8977
|
+
console.log(BOLD("Automation readiness"));
|
|
8978
|
+
console.log(chalk.white(`- Contract creation tool: ${hasContractTool ? "available" : "missing"}`));
|
|
8979
|
+
console.log(chalk.white(`- Path check tool: ${hasPathTool ? "available" : "missing"}`));
|
|
8980
|
+
console.log(chalk.white(`- Approval tool: ${hasApprovalTool ? "available" : "missing"}`));
|
|
8981
|
+
console.log(chalk.white(`- Finish guidance tool: ${hasFinishTool ? "available" : "missing"}`));
|
|
8982
|
+
console.log(chalk.white(`- Local artifacts: ${artifactCount}`));
|
|
8983
|
+
console.log("");
|
|
8984
|
+
console.log(BOLD("Readiness"));
|
|
8985
|
+
console.log(chalk.white(`- State: ${readiness}`));
|
|
8986
|
+
console.log("");
|
|
8987
|
+
console.log(BOLD("Next"));
|
|
8988
|
+
if (repoCheck.status === "blocked_repair") {
|
|
8989
|
+
console.log(chalk.yellow("- RunTrim local state needs repair. Run runtrim repo repair."));
|
|
8990
|
+
} else if (repoCheck.status === "blocked_limit") {
|
|
8991
|
+
console.log(chalk.yellow("- Free includes 1 tracked repo. Continue in tracked repo, unlink with runtrim repo unlink --force, or upgrade to Builder."));
|
|
8992
|
+
} else if (readiness === "ready") {
|
|
8993
|
+
if (claudeMcpState !== "configured" && cursorMcpState !== "configured") {
|
|
8994
|
+
console.log(chalk.white("- Ready locally, MCP client not connected."));
|
|
8995
|
+
console.log(chalk.white("- Run runtrim mcp instructions or copy .runtrim/mcp/cursor.json into your MCP client."));
|
|
8996
|
+
} else {
|
|
8997
|
+
console.log(chalk.white("- Your project is RunTrim-aware. Open Cursor/Claude/Codex and use normal language."));
|
|
8998
|
+
}
|
|
8999
|
+
} else {
|
|
9000
|
+
console.log(chalk.white("- Run runtrim start."));
|
|
9001
|
+
console.log(chalk.white("- Run runtrim mcp instructions."));
|
|
9002
|
+
console.log(chalk.white("- Run runtrim mcp config --print."));
|
|
9003
|
+
}
|
|
9004
|
+
if (artifactCount > 25) {
|
|
9005
|
+
console.log(chalk.white(`- Local artifacts: ${artifactCount}. Run runtrim clean --dry-run to review cleanup.`));
|
|
9006
|
+
}
|
|
9007
|
+
console.log("");
|
|
9008
|
+
});
|
|
8553
9009
|
var PROTOCOL_BLOCK_START = "<!-- RUNTRIM_PROTOCOL_START -->";
|
|
8554
9010
|
var PROTOCOL_BLOCK_END = "<!-- RUNTRIM_PROTOCOL_END -->";
|
|
8555
9011
|
var PROTOCOL_POINTER_BLOCK = `
|
|
@@ -9654,6 +10110,146 @@ agentCommand.command("prompt-mode <mode>").description("Set how guarded contract
|
|
|
9654
10110
|
console.log(ACCENT.bold(" Agent prompt mode updated to: " + m));
|
|
9655
10111
|
console.log("");
|
|
9656
10112
|
});
|
|
10113
|
+
var ciCommand = program.command("ci").description("RunTrim CI checks for pull requests and merges");
|
|
10114
|
+
ciCommand.command("check").description("Evaluate changed files and RunTrim context for CI-safe PASS/WARN/BLOCKED verdict").option("--base <ref>", "Base ref to diff from").option("--head <ref>", "Head ref to diff to").option("--strict", "Treat WARN as failing and require RunTrim context").option("--allow-warn", "Allow WARN even when --strict is set").option("--json", "Print machine-readable JSON output").option("--report", "Include extra diagnostic context in output").action(async (options) => {
|
|
10115
|
+
const cwd = process.cwd();
|
|
10116
|
+
const strict = options.strict === true;
|
|
10117
|
+
const allowWarn = options.allowWarn === true;
|
|
10118
|
+
const outputJson = options.json === true;
|
|
10119
|
+
const report = options.report === true;
|
|
10120
|
+
const repoCheck = await assertFreeRepoAllowed(cwd);
|
|
10121
|
+
const detection = await detectCiChangedFiles(cwd, options.base, options.head);
|
|
10122
|
+
const changedFiles = detection.files;
|
|
10123
|
+
const contract = parseContractSummary(cwd);
|
|
10124
|
+
const latestRun = loadLatestRun(cwd);
|
|
10125
|
+
const hasRuntrimContext = contract.exists || Boolean(latestRun);
|
|
10126
|
+
const issues = [];
|
|
10127
|
+
const warnings = [...detection.warnings];
|
|
10128
|
+
const nextSteps = [];
|
|
10129
|
+
let verdict = "PASS";
|
|
10130
|
+
if (repoCheck.status === "blocked_repair") {
|
|
10131
|
+
issues.push("RunTrim local state needs repair before CI can trust local guard state.");
|
|
10132
|
+
nextSteps.push("Run: runtrim repo repair");
|
|
10133
|
+
verdict = "BLOCKED";
|
|
10134
|
+
} else if (repoCheck.status === "blocked_limit") {
|
|
10135
|
+
issues.push("Free includes 1 tracked repo and this repo is not currently tracked.");
|
|
10136
|
+
nextSteps.push("Continue in tracked repo, unlink with runtrim repo unlink --force, or upgrade to Builder.");
|
|
10137
|
+
verdict = "BLOCKED";
|
|
10138
|
+
}
|
|
10139
|
+
if (changedFiles.length === 0) {
|
|
10140
|
+
warnings.push("No changed files detected for this diff.");
|
|
10141
|
+
}
|
|
10142
|
+
const secretFiles = changedFiles.filter(isSecretLikePath);
|
|
10143
|
+
if (secretFiles.length > 0) {
|
|
10144
|
+
issues.push(`Sensitive file path changed: ${secretFiles[0]}`);
|
|
10145
|
+
nextSteps.push("Remove secret/env/key/cert files from this PR before merge.");
|
|
10146
|
+
verdict = "BLOCKED";
|
|
10147
|
+
}
|
|
10148
|
+
const highRiskFiles = changedFiles.filter((f) => isHighRiskLogicPath(f) && !isSecretLikePath(f));
|
|
10149
|
+
if (highRiskFiles.length > 0) {
|
|
10150
|
+
if (!hasRuntrimContext) {
|
|
10151
|
+
issues.push(`High-risk path changed without RunTrim context: ${highRiskFiles[0]}`);
|
|
10152
|
+
nextSteps.push("Create a dedicated guarded run and finish it before merging.");
|
|
10153
|
+
verdict = "BLOCKED";
|
|
10154
|
+
} else if (contract.allowedPaths.length > 0 && !matchesAnyContractRule(highRiskFiles[0], contract.allowedPaths)) {
|
|
10155
|
+
issues.push(`High-risk path is outside contract allowed scope: ${highRiskFiles[0]}`);
|
|
10156
|
+
nextSteps.push(`Run: runtrim approve "Allow ${highRiskFiles[0]} for this run only"`);
|
|
10157
|
+
verdict = "BLOCKED";
|
|
10158
|
+
}
|
|
10159
|
+
}
|
|
10160
|
+
if (contract.forbiddenPaths.length > 0) {
|
|
10161
|
+
const forbiddenTouched = changedFiles.filter((f) => matchesAnyContractRule(f, contract.forbiddenPaths));
|
|
10162
|
+
if (forbiddenTouched.length > 0) {
|
|
10163
|
+
issues.push(`Contract-forbidden path touched: ${forbiddenTouched[0]}`);
|
|
10164
|
+
nextSteps.push("Split the task or get explicit scoped approval before merge.");
|
|
10165
|
+
verdict = "BLOCKED";
|
|
10166
|
+
}
|
|
10167
|
+
}
|
|
10168
|
+
if (contract.allowedPaths.length > 0) {
|
|
10169
|
+
const outOfScope = changedFiles.filter((f) => !matchesAnyContractRule(f, contract.allowedPaths));
|
|
10170
|
+
if (outOfScope.length > 0) {
|
|
10171
|
+
issues.push(`Changed file is outside latest contract scope: ${outOfScope[0]}`);
|
|
10172
|
+
nextSteps.push(`Run: runtrim approve "Allow ${outOfScope[0]} for this run only"`);
|
|
10173
|
+
verdict = "BLOCKED";
|
|
10174
|
+
}
|
|
10175
|
+
}
|
|
10176
|
+
const docsOnly = changedFiles.length > 0 && changedFiles.every((f) => isDocsLikePath(f));
|
|
10177
|
+
if (!hasRuntrimContext && verdict !== "BLOCKED") {
|
|
10178
|
+
if (docsOnly) {
|
|
10179
|
+
warnings.push("No RunTrim contract/report found. Docs-only change treated as low risk.");
|
|
10180
|
+
verdict = "WARN";
|
|
10181
|
+
} else {
|
|
10182
|
+
warnings.push("No RunTrim contract/report found. CI could not fully verify scope context.");
|
|
10183
|
+
verdict = "WARN";
|
|
10184
|
+
}
|
|
10185
|
+
}
|
|
10186
|
+
if (strict && !hasRuntrimContext && verdict !== "BLOCKED") {
|
|
10187
|
+
issues.push("Strict mode requires RunTrim contract/report context.");
|
|
10188
|
+
verdict = "BLOCKED";
|
|
10189
|
+
}
|
|
10190
|
+
if (verdict === "PASS" && warnings.length > 0) {
|
|
10191
|
+
verdict = "WARN";
|
|
10192
|
+
}
|
|
10193
|
+
if (nextSteps.length === 0) {
|
|
10194
|
+
if (verdict === "PASS") {
|
|
10195
|
+
nextSteps.push("Safe to merge under current RunTrim CI policy.");
|
|
10196
|
+
} else if (verdict === "WARN") {
|
|
10197
|
+
nextSteps.push("Run runtrim finish locally to strengthen verification context.");
|
|
10198
|
+
} else {
|
|
10199
|
+
nextSteps.push("Resolve blocked issues, then rerun runtrim ci check.");
|
|
10200
|
+
}
|
|
10201
|
+
}
|
|
10202
|
+
let exitCode = 0;
|
|
10203
|
+
if (verdict === "BLOCKED") exitCode = 1;
|
|
10204
|
+
if (verdict === "WARN" && strict && !allowWarn) exitCode = 1;
|
|
10205
|
+
const jsonPayload = {
|
|
10206
|
+
verdict: verdict.toLowerCase(),
|
|
10207
|
+
exitCode,
|
|
10208
|
+
changedFiles,
|
|
10209
|
+
issues,
|
|
10210
|
+
warnings,
|
|
10211
|
+
nextSteps
|
|
10212
|
+
};
|
|
10213
|
+
if (outputJson) {
|
|
10214
|
+
process.stdout.write(`${JSON.stringify(jsonPayload, null, 2)}
|
|
10215
|
+
`);
|
|
10216
|
+
process.exit(exitCode);
|
|
10217
|
+
return;
|
|
10218
|
+
}
|
|
10219
|
+
console.log("");
|
|
10220
|
+
console.log(BOLD("RunTrim") + DIM(" CI Check"));
|
|
10221
|
+
console.log("");
|
|
10222
|
+
const verdictColor = verdict === "PASS" ? chalk.green : verdict === "WARN" ? chalk.yellow : chalk.red;
|
|
10223
|
+
console.log(DIM(" Verdict: ") + verdictColor(verdict));
|
|
10224
|
+
console.log("");
|
|
10225
|
+
console.log(DIM(" Changed files:"));
|
|
10226
|
+
if (changedFiles.length === 0) console.log(chalk.white(" - (none detected)"));
|
|
10227
|
+
for (const f of changedFiles.slice(0, 20)) console.log(chalk.white(" - " + f));
|
|
10228
|
+
if (changedFiles.length > 20) console.log(DIM(` ... and ${changedFiles.length - 20} more`));
|
|
10229
|
+
console.log("");
|
|
10230
|
+
if (issues.length > 0) {
|
|
10231
|
+
console.log(DIM(" Issues:"));
|
|
10232
|
+
for (const i of issues) console.log(chalk.red(" - " + i));
|
|
10233
|
+
console.log("");
|
|
10234
|
+
}
|
|
10235
|
+
if (warnings.length > 0) {
|
|
10236
|
+
console.log(DIM(" Warnings:"));
|
|
10237
|
+
for (const w of warnings) console.log(chalk.yellow(" - " + w));
|
|
10238
|
+
console.log("");
|
|
10239
|
+
}
|
|
10240
|
+
console.log(DIM(" Next:"));
|
|
10241
|
+
for (const n of nextSteps) console.log(chalk.white(" - " + n));
|
|
10242
|
+
if (report) {
|
|
10243
|
+
console.log("");
|
|
10244
|
+
console.log(DIM(" Report:"));
|
|
10245
|
+
console.log(chalk.white(` - Base: ${detection.baseUsed}`));
|
|
10246
|
+
console.log(chalk.white(` - Head: ${detection.headUsed}`));
|
|
10247
|
+
console.log(chalk.white(` - Contract: ${contract.exists ? contract.active ? "active" : "present" : "none"}`));
|
|
10248
|
+
console.log(chalk.white(` - Latest run: ${latestRun ? latestRun.status : "none"}`));
|
|
10249
|
+
}
|
|
10250
|
+
console.log("");
|
|
10251
|
+
process.exit(exitCode);
|
|
10252
|
+
});
|
|
9657
10253
|
var authCommand = program.command("auth").description("Configure sync token for dashboard sync");
|
|
9658
10254
|
authCommand.command("set <token>").description("Set sync token and enable sync").action((token) => {
|
|
9659
10255
|
const cwd = process.cwd();
|
|
@@ -9795,6 +10391,214 @@ repoCommand.command("unlink").description("Unlink tracked repo from local free-p
|
|
|
9795
10391
|
console.log(DIM(" No tracked repo found."));
|
|
9796
10392
|
console.log("");
|
|
9797
10393
|
});
|
|
10394
|
+
program.command("clean").description("Clean old local RunTrim artifacts while preserving active project state").option("--dry-run", "Preview files that would be removed").option("--keep <n>", "Number of latest run-linked artifacts to keep", "10").action(async (options) => {
|
|
10395
|
+
var _a2;
|
|
10396
|
+
const cwd = process.cwd();
|
|
10397
|
+
const dryRun = (options == null ? void 0 : options.dryRun) === true;
|
|
10398
|
+
const keep = Math.max(1, Number.parseInt((_a2 = options == null ? void 0 : options.keep) != null ? _a2 : "10", 10) || 10);
|
|
10399
|
+
ensureInternalArtifactDirs(cwd);
|
|
10400
|
+
const runs = loadAllRuns(cwd);
|
|
10401
|
+
const keepRunIds = new Set(runs.slice(0, keep).map((r) => r.id));
|
|
10402
|
+
const files = listArtifactFiles(cwd);
|
|
10403
|
+
const removable = files.filter((filePath) => {
|
|
10404
|
+
const runId = parseRunIdFromArtifact(filePath);
|
|
10405
|
+
if (!runId) return true;
|
|
10406
|
+
return !keepRunIds.has(runId);
|
|
10407
|
+
});
|
|
10408
|
+
const byCategory = {
|
|
10409
|
+
runs: 0,
|
|
10410
|
+
previews: 0,
|
|
10411
|
+
restores: 0,
|
|
10412
|
+
archives: 0,
|
|
10413
|
+
other: 0
|
|
10414
|
+
};
|
|
10415
|
+
for (const filePath of removable) {
|
|
10416
|
+
const rel = import_path13.default.relative(cwd, filePath).replace(/\\/g, "/").toLowerCase();
|
|
10417
|
+
if (rel.includes(".runtrim/internal/runs/") || rel.includes(".runtrim/runs/")) byCategory.runs += 1;
|
|
10418
|
+
else if (rel.includes(".runtrim/internal/previews/") || rel.includes(".runtrim/previews/")) byCategory.previews += 1;
|
|
10419
|
+
else if (rel.includes(".runtrim/internal/restores/") || rel.includes(".runtrim/restores/")) byCategory.restores += 1;
|
|
10420
|
+
else if (rel.includes("archive")) byCategory.archives += 1;
|
|
10421
|
+
else byCategory.other += 1;
|
|
10422
|
+
}
|
|
10423
|
+
if (!dryRun) {
|
|
10424
|
+
for (const filePath of removable) {
|
|
10425
|
+
try {
|
|
10426
|
+
import_fs13.default.rmSync(filePath, { force: true });
|
|
10427
|
+
} catch (e) {
|
|
10428
|
+
}
|
|
10429
|
+
}
|
|
10430
|
+
}
|
|
10431
|
+
console.log("");
|
|
10432
|
+
console.log(BOLD("RunTrim") + DIM(" clean"));
|
|
10433
|
+
console.log("");
|
|
10434
|
+
console.log(DIM(" Mode ") + chalk.white(dryRun ? "dry-run" : "apply"));
|
|
10435
|
+
console.log(DIM(" Keep latest runs ") + chalk.white(String(keep)));
|
|
10436
|
+
console.log(DIM(" Candidate files ") + chalk.white(String(removable.length)));
|
|
10437
|
+
console.log("");
|
|
10438
|
+
console.log(DIM(" Cleanup summary"));
|
|
10439
|
+
console.log(chalk.white(` - runs: ${byCategory.runs}`));
|
|
10440
|
+
console.log(chalk.white(` - previews: ${byCategory.previews}`));
|
|
10441
|
+
console.log(chalk.white(` - restores: ${byCategory.restores}`));
|
|
10442
|
+
console.log(chalk.white(` - archives: ${byCategory.archives}`));
|
|
10443
|
+
if (byCategory.other > 0) console.log(chalk.white(` - other: ${byCategory.other}`));
|
|
10444
|
+
console.log("");
|
|
10445
|
+
if (removable.length > 0) {
|
|
10446
|
+
const sample = removable.slice(0, 10).map((p) => import_path13.default.relative(cwd, p).replace(/\\/g, "/"));
|
|
10447
|
+
console.log(DIM(" Sample files"));
|
|
10448
|
+
for (const rel of sample) console.log(chalk.white(` - ${rel}`));
|
|
10449
|
+
if (removable.length > sample.length) {
|
|
10450
|
+
console.log(DIM(` ... and ${removable.length - sample.length} more`));
|
|
10451
|
+
}
|
|
10452
|
+
console.log("");
|
|
10453
|
+
}
|
|
10454
|
+
if (dryRun) {
|
|
10455
|
+
console.log(chalk.white(" No files were removed."));
|
|
10456
|
+
console.log(chalk.white(" Re-run without --dry-run to apply cleanup."));
|
|
10457
|
+
} else {
|
|
10458
|
+
console.log(chalk.white(" Cleanup complete."));
|
|
10459
|
+
console.log(chalk.white(" Active contract, latest files, memory current, and MCP snippets were preserved."));
|
|
10460
|
+
}
|
|
10461
|
+
console.log("");
|
|
10462
|
+
});
|
|
10463
|
+
var restoreCommand = program.command("restore").description("Preview or apply local restore for guarded runs");
|
|
10464
|
+
restoreCommand.argument("[runId]", "Run ID to restore, or use 'last'").option("--preview", "Preview restore plan").option("--apply", "Apply restore plan").option("--force", "Apply even if unrelated new changes are detected").action(async (runIdArg, options) => {
|
|
10465
|
+
var _a2, _b, _c, _d;
|
|
10466
|
+
const cwd = process.cwd();
|
|
10467
|
+
const doPreview = (options == null ? void 0 : options.preview) === true;
|
|
10468
|
+
const doApply = (options == null ? void 0 : options.apply) === true;
|
|
10469
|
+
const force = (options == null ? void 0 : options.force) === true;
|
|
10470
|
+
if (!doPreview && !doApply) {
|
|
10471
|
+
console.log("");
|
|
10472
|
+
console.log(chalk.yellow("Choose --preview or --apply."));
|
|
10473
|
+
console.log("");
|
|
10474
|
+
return;
|
|
10475
|
+
}
|
|
10476
|
+
if (doPreview && doApply) {
|
|
10477
|
+
console.log("");
|
|
10478
|
+
console.log(chalk.yellow("Use either --preview or --apply, not both."));
|
|
10479
|
+
console.log("");
|
|
10480
|
+
return;
|
|
10481
|
+
}
|
|
10482
|
+
const runs = loadAllRuns(cwd);
|
|
10483
|
+
const runIdInput = (runIdArg != null ? runIdArg : "last").trim();
|
|
10484
|
+
let targetRunId = runIdInput;
|
|
10485
|
+
if (runIdInput === "last") {
|
|
10486
|
+
const latest = runs.find((r) => r.status === "completed" || r.status === "guarded" || r.status === "executed" || r.status === "checked");
|
|
10487
|
+
if (!latest) {
|
|
10488
|
+
console.log("");
|
|
10489
|
+
console.log(chalk.yellow("No runs available for restore."));
|
|
10490
|
+
console.log("");
|
|
10491
|
+
return;
|
|
10492
|
+
}
|
|
10493
|
+
targetRunId = latest.id;
|
|
10494
|
+
}
|
|
10495
|
+
const run = runs.find((r) => r.id === targetRunId);
|
|
10496
|
+
if (!run) {
|
|
10497
|
+
console.log("");
|
|
10498
|
+
console.log(chalk.yellow(`Run not found: ${targetRunId}`));
|
|
10499
|
+
console.log("");
|
|
10500
|
+
return;
|
|
10501
|
+
}
|
|
10502
|
+
const restore = loadRestorePoint(cwd, targetRunId);
|
|
10503
|
+
if (!restore) {
|
|
10504
|
+
console.log("");
|
|
10505
|
+
console.log(chalk.yellow("No restore point found for this run."));
|
|
10506
|
+
console.log("");
|
|
10507
|
+
return;
|
|
10508
|
+
}
|
|
10509
|
+
const postFiles = (_b = (_a2 = restore.postRun) == null ? void 0 : _a2.changedFiles) != null ? _b : [];
|
|
10510
|
+
const sensitive = postFiles.filter((f) => isSensitivePath(f) || isSecretLikePath(f));
|
|
10511
|
+
const safeFiles = postFiles.filter((f) => !sensitive.includes(f));
|
|
10512
|
+
const gitAvailable = await isGitRepo(cwd);
|
|
10513
|
+
const method = gitAvailable && restore.preRun.commit ? "git checkout from pre-run commit" : "metadata-only (limited)";
|
|
10514
|
+
const nowChanged = gitAvailable ? dedupeFiles((await getGitChangedFiles(cwd)).map((f) => f.path)) : [];
|
|
10515
|
+
const unrelated = nowChanged.filter((f) => !postFiles.includes(f));
|
|
10516
|
+
if (doPreview) {
|
|
10517
|
+
console.log("");
|
|
10518
|
+
console.log(BOLD("RunTrim") + DIM(" restore preview"));
|
|
10519
|
+
console.log("");
|
|
10520
|
+
console.log(DIM(" Run ID ") + chalk.white(targetRunId));
|
|
10521
|
+
console.log(DIM(" Task ") + chalk.white(truncate(run.task, 80)));
|
|
10522
|
+
console.log(DIM(" Method ") + chalk.white(method));
|
|
10523
|
+
console.log(DIM(" Verdict ") + chalk.white((_d = (_c = restore.postRun) == null ? void 0 : _c.finishVerdict) != null ? _d : "unknown"));
|
|
10524
|
+
console.log("");
|
|
10525
|
+
console.log(DIM(" Files to restore"));
|
|
10526
|
+
if (safeFiles.length === 0) console.log(chalk.white(" - (none)"));
|
|
10527
|
+
for (const f of safeFiles.slice(0, 20)) console.log(chalk.white(" - " + f));
|
|
10528
|
+
if (safeFiles.length > 20) console.log(DIM(` ... and ${safeFiles.length - 20} more`));
|
|
10529
|
+
if (sensitive.length > 0) {
|
|
10530
|
+
console.log("");
|
|
10531
|
+
console.log(DIM(" Sensitive files (listed by path only)"));
|
|
10532
|
+
for (const f of sensitive.slice(0, 20)) console.log(chalk.yellow(" - " + f));
|
|
10533
|
+
console.log(chalk.yellow(" Sensitive files are skipped by default and not auto-restored."));
|
|
10534
|
+
}
|
|
10535
|
+
if (unrelated.length > 0) {
|
|
10536
|
+
console.log("");
|
|
10537
|
+
console.log(chalk.yellow(" Warning: repo has new changes after this run."));
|
|
10538
|
+
console.log(chalk.yellow(" Use --apply --force or review manually before restore."));
|
|
10539
|
+
}
|
|
10540
|
+
console.log("");
|
|
10541
|
+
return;
|
|
10542
|
+
}
|
|
10543
|
+
if (!doApply) return;
|
|
10544
|
+
if (!gitAvailable || !restore.preRun.commit) {
|
|
10545
|
+
console.log("");
|
|
10546
|
+
console.log(chalk.red("Restore apply requires git and a pre-run commit restore point."));
|
|
10547
|
+
console.log("");
|
|
10548
|
+
process.exit(1);
|
|
10549
|
+
return;
|
|
10550
|
+
}
|
|
10551
|
+
if (unrelated.length > 0 && !force) {
|
|
10552
|
+
console.log("");
|
|
10553
|
+
console.log(chalk.red("Restore blocked: new unrelated changes detected after this run."));
|
|
10554
|
+
console.log(chalk.red("Re-run with --apply --force after manual review."));
|
|
10555
|
+
console.log("");
|
|
10556
|
+
process.exit(1);
|
|
10557
|
+
return;
|
|
10558
|
+
}
|
|
10559
|
+
const restored = [];
|
|
10560
|
+
const skippedSensitive = [...sensitive];
|
|
10561
|
+
const failed = [];
|
|
10562
|
+
for (const file of safeFiles) {
|
|
10563
|
+
try {
|
|
10564
|
+
let existedBefore = false;
|
|
10565
|
+
try {
|
|
10566
|
+
await (0, import_execa3.execa)("git", ["cat-file", "-e", `${restore.preRun.commit}:${file}`], { cwd });
|
|
10567
|
+
existedBefore = true;
|
|
10568
|
+
} catch (e) {
|
|
10569
|
+
existedBefore = false;
|
|
10570
|
+
}
|
|
10571
|
+
if (existedBefore) {
|
|
10572
|
+
await (0, import_execa3.execa)("git", ["checkout", restore.preRun.commit, "--", file], { cwd });
|
|
10573
|
+
} else if (import_fs13.default.existsSync(import_path13.default.join(cwd, file))) {
|
|
10574
|
+
import_fs13.default.rmSync(import_path13.default.join(cwd, file), { force: true });
|
|
10575
|
+
}
|
|
10576
|
+
restored.push(file);
|
|
10577
|
+
} catch (e) {
|
|
10578
|
+
failed.push(file);
|
|
10579
|
+
}
|
|
10580
|
+
}
|
|
10581
|
+
const report = {
|
|
10582
|
+
runId: targetRunId,
|
|
10583
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10584
|
+
restored,
|
|
10585
|
+
skippedSensitive,
|
|
10586
|
+
failed,
|
|
10587
|
+
forced: force
|
|
10588
|
+
};
|
|
10589
|
+
const reportPath = import_path13.default.join(getRestoresDir(cwd), `${targetRunId}.report.${Date.now()}.json`);
|
|
10590
|
+
import_fs13.default.writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
10591
|
+
console.log("");
|
|
10592
|
+
console.log(BOLD("RunTrim") + DIM(" restore apply"));
|
|
10593
|
+
console.log("");
|
|
10594
|
+
console.log(DIM(" Run ID ") + chalk.white(targetRunId));
|
|
10595
|
+
console.log(DIM(" Restored files ") + chalk.white(String(restored.length)));
|
|
10596
|
+
console.log(DIM(" Skipped sensitive ") + chalk.white(String(skippedSensitive.length)));
|
|
10597
|
+
console.log(DIM(" Failed ") + chalk.white(String(failed.length)));
|
|
10598
|
+
console.log(DIM(" Report ") + chalk.white(import_path13.default.relative(cwd, reportPath)));
|
|
10599
|
+
console.log("");
|
|
10600
|
+
if (failed.length > 0) process.exit(1);
|
|
10601
|
+
});
|
|
9798
10602
|
program.command("guard <task>").description("Audit a task and generate a guarded run contract").action(async (task) => {
|
|
9799
10603
|
var _a2, _b, _c;
|
|
9800
10604
|
const cwd = process.cwd();
|
|
@@ -9882,7 +10686,7 @@ program.command("guard <task>").description("Audit a task and generate a guarded
|
|
|
9882
10686
|
const run2 = saveRun(task, audit, contract, cwd);
|
|
9883
10687
|
updateRun(run2.id, { status: "blocked" }, cwd);
|
|
9884
10688
|
console.log("");
|
|
9885
|
-
console.log(DIM(" Run saved: .runtrim/runs/" + run2.id + ".json (status: blocked)"));
|
|
10689
|
+
console.log(DIM(" Run saved: .runtrim/internal/runs/" + run2.id + ".json (status: blocked)"));
|
|
9886
10690
|
console.log("");
|
|
9887
10691
|
console.log(DIM(" Next: ") + chalk.white('runtrim guard "<one system at a time>"'));
|
|
9888
10692
|
console.log("");
|
|
@@ -9962,7 +10766,7 @@ program.command("guard <task>").description("Audit a task and generate a guarded
|
|
|
9962
10766
|
}
|
|
9963
10767
|
const run = saveRun(task, audit, contract, cwd);
|
|
9964
10768
|
console.log("");
|
|
9965
|
-
console.log(DIM(" Run saved: .runtrim/runs/" + run.id + ".json"));
|
|
10769
|
+
console.log(DIM(" Run saved: .runtrim/internal/runs/" + run.id + ".json"));
|
|
9966
10770
|
console.log("");
|
|
9967
10771
|
console.log(DIM(" After your agent run: ") + chalk.white("runtrim check"));
|
|
9968
10772
|
console.log("");
|
|
@@ -10012,6 +10816,7 @@ program.command("run <task>").description("Guard then run configured local agent
|
|
|
10012
10816
|
},
|
|
10013
10817
|
cwd
|
|
10014
10818
|
);
|
|
10819
|
+
await captureRestorePoint(cwd, run.id, task);
|
|
10015
10820
|
const copySavings = estimateSavingsFromTokens2(
|
|
10016
10821
|
parseEstimatedNumber3(String(contract.estimatedTokensTrimmed))
|
|
10017
10822
|
);
|
|
@@ -10092,6 +10897,7 @@ program.command("run <task>").description("Guard then run configured local agent
|
|
|
10092
10897
|
},
|
|
10093
10898
|
cwd
|
|
10094
10899
|
);
|
|
10900
|
+
await captureRestorePoint(cwd, run.id, task);
|
|
10095
10901
|
console.log(DIM(" Execution cancelled. Guarded contract copied to clipboard."));
|
|
10096
10902
|
console.log("");
|
|
10097
10903
|
return;
|
|
@@ -10148,6 +10954,7 @@ program.command("run <task>").description("Guard then run configured local agent
|
|
|
10148
10954
|
},
|
|
10149
10955
|
cwd
|
|
10150
10956
|
);
|
|
10957
|
+
await captureRestorePoint(cwd, run.id, task);
|
|
10151
10958
|
console.log("");
|
|
10152
10959
|
console.log(chalk.yellow(" Agent command not found. Falling back to copy mode guidance."));
|
|
10153
10960
|
console.log(DIM(" Configure with: npm run runtrim -- agent set claude|codex|custom"));
|
|
@@ -10181,6 +10988,7 @@ program.command("run <task>").description("Guard then run configured local agent
|
|
|
10181
10988
|
},
|
|
10182
10989
|
cwd
|
|
10183
10990
|
);
|
|
10991
|
+
await captureRestorePoint(cwd, run.id, task);
|
|
10184
10992
|
console.log("");
|
|
10185
10993
|
console.log(chalk.yellow(" Agent command not found. Falling back to copy mode guidance."));
|
|
10186
10994
|
console.log(DIM(" Configure with: npm run runtrim -- agent set claude|codex|custom"));
|
|
@@ -10224,7 +11032,7 @@ ${stderr}`, "utf-8");
|
|
|
10224
11032
|
exitCode,
|
|
10225
11033
|
stdoutPreview: stdout.slice(0, OUTPUT_PREVIEW_MAX),
|
|
10226
11034
|
stderrPreview: stderr.slice(0, OUTPUT_PREVIEW_MAX),
|
|
10227
|
-
outputPath: `.runtrim/runs/${run.id}.output.txt`
|
|
11035
|
+
outputPath: `.runtrim/internal/runs/${run.id}.output.txt`
|
|
10228
11036
|
},
|
|
10229
11037
|
evaluation
|
|
10230
11038
|
},
|
|
@@ -10374,6 +11182,7 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
|
|
|
10374
11182
|
memoryUsed,
|
|
10375
11183
|
providerRouting
|
|
10376
11184
|
}, cwd);
|
|
11185
|
+
await captureRestorePoint(cwd, run.id, task);
|
|
10377
11186
|
const bridgeCtx = deriveBridgeContext(task, contract, runs, projectName);
|
|
10378
11187
|
let bridgeWritten = [];
|
|
10379
11188
|
let bridgeManagedPaths = [];
|
|
@@ -10432,7 +11241,7 @@ program.command("go <task>").description("Bridge Mode: generate a scoped contrac
|
|
|
10432
11241
|
if (contract.contract.stopRules.length > 0) {
|
|
10433
11242
|
console.log(DIM(" Stop rule ") + chalk.white(truncate((_g = contract.contract.stopRules[contract.contract.stopRules.length - 1]) != null ? _g : "", 60)));
|
|
10434
11243
|
}
|
|
10435
|
-
console.log(DIM(" Run saved ") + chalk.white(`.runtrim/runs/${run.id}.json`));
|
|
11244
|
+
console.log(DIM(" Run saved ") + chalk.white(`.runtrim/internal/runs/${run.id}.json`));
|
|
10436
11245
|
console.log(DIM(" Contract ") + chalk.white("created"));
|
|
10437
11246
|
console.log("");
|
|
10438
11247
|
if (bridgeWritten.length > 0) {
|
|
@@ -11787,6 +12596,18 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
|
|
|
11787
12596
|
const blockedByExistingHard = scope.forbiddenFiles.length > 0 || scope.status === "limit_exceeded";
|
|
11788
12597
|
const warnBySensitiveIgnored = sensitiveIgnored.length > 0;
|
|
11789
12598
|
const finishVerdict = blockedBySensitive || blockedByContract || blockedByExistingHard ? "BLOCKED" : warnBySensitiveIgnored || scopeDriftStatus !== "passed" || evaluation.status === "needs_verification" || evaluation.status === "partial" ? "WARN" : "PASS";
|
|
12599
|
+
const restoreRecord = loadRestorePoint(cwd, activeRun.id);
|
|
12600
|
+
if (restoreRecord) {
|
|
12601
|
+
restoreRecord.postRun = {
|
|
12602
|
+
changedFiles,
|
|
12603
|
+
forbiddenFiles: [...scope.forbiddenFiles, ...forbiddenPathFiles],
|
|
12604
|
+
sensitiveFiles: scope.sensitiveFiles,
|
|
12605
|
+
outOfScopeFiles: [...outOfContractFiles, ...scope.outOfScopeFiles],
|
|
12606
|
+
finishVerdict,
|
|
12607
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12608
|
+
};
|
|
12609
|
+
saveRestorePoint(cwd, restoreRecord);
|
|
12610
|
+
}
|
|
11790
12611
|
const verdictColor = finishVerdict === "PASS" ? chalk.green : finishVerdict === "WARN" ? chalk.yellow : chalk.red;
|
|
11791
12612
|
const scopeColor = scopeDriftStatus === "passed" ? chalk.green : scopeDriftStatus === "forbidden_touched" ? chalk.red : chalk.yellow;
|
|
11792
12613
|
const riskAfter = (_m = activeRun.contract.wasteRiskAfter) != null ? _m : "medium";
|