runtrim 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cli/runtrim.cjs +642 -87
- package/dist-cli/runtrim.js +642 -87
- package/package.json +1 -1
package/dist-cli/runtrim.js
CHANGED
|
@@ -148,6 +148,22 @@ var ENV_FILE_RE = /(?:^|[\s"'`,(])(\.[.]?env(?:\.[a-zA-Z\d]+)?)\b/g;
|
|
|
148
148
|
var ONLY_EDIT_RE = /\bonly\s+(?:edit|touch|modify|change|update|fix)\b/i;
|
|
149
149
|
var MUST_INCLUDE_RE = /\ballowed\s+scope\s+(?:must\s+)?include\b|\bmust\s+(?:include|contain)\b/i;
|
|
150
150
|
var CLI_SCOPE_RE = /\b(cli|command routing|runtrim command|run compiler|contract generation|scope inference|preview command|agent preview command|agent apply|adapters?|auto-guard|bridge helpers?|daemon|local server|localhost|\.runtrim(?:\s+artifacts?)?)\b/i;
|
|
151
|
+
var NEGATION_PREFIX_RE = /\b(do not|don't|dont|never|avoid|must not|should not|without changing|without touching|no changes to|keep .* untouched|leave .* untouched|keep .* unchanged)\b/i;
|
|
152
|
+
function hasNegationNear(text, index) {
|
|
153
|
+
const start = Math.max(0, index - 64);
|
|
154
|
+
const window = text.slice(start, index + 8);
|
|
155
|
+
return NEGATION_PREFIX_RE.test(window);
|
|
156
|
+
}
|
|
157
|
+
function hasPositiveKeywordMention(task, keyword) {
|
|
158
|
+
const lowerTask = task.toLowerCase();
|
|
159
|
+
const lowerKeyword = keyword.toLowerCase();
|
|
160
|
+
let idx = lowerTask.indexOf(lowerKeyword);
|
|
161
|
+
while (idx !== -1) {
|
|
162
|
+
if (!hasNegationNear(lowerTask, idx)) return true;
|
|
163
|
+
idx = lowerTask.indexOf(lowerKeyword, idx + lowerKeyword.length);
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
151
167
|
function extractScopePhrase(task, re) {
|
|
152
168
|
var _a2, _b;
|
|
153
169
|
const m = task.match(re);
|
|
@@ -205,18 +221,34 @@ function buildExplicitAllowedScope(task, explicitPaths) {
|
|
|
205
221
|
}
|
|
206
222
|
function buildExplicitForbiddenScope(task) {
|
|
207
223
|
const out = [];
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
224
|
+
const addBoundaryList = (raw) => {
|
|
225
|
+
if (!raw) return;
|
|
226
|
+
const normalized = raw.replace(/\b(logic|internals?|behavior|files?|systems?)\b/gi, "").replace(/\s+/g, " ").trim();
|
|
227
|
+
const parts = normalized.split(/\s*(?:,|;|\band\b|\bor\b)\s*/i).map((p) => p.trim().replace(/[.]+$/, "")).filter(Boolean).slice(0, 12);
|
|
228
|
+
for (const p of parts) {
|
|
229
|
+
if (p.length < 2) continue;
|
|
230
|
+
out.push(`Do not touch ${p}`);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
const explicitPhrases = [
|
|
234
|
+
/\bforbidden\s+scope\s+must\s+include\s+([^\n.]+)/i,
|
|
235
|
+
/\bdo\s+not\s+touch\s+([^\n.]+)/i,
|
|
236
|
+
/\bdo\s+not\s+edit\s+([^\n.]+)/i,
|
|
237
|
+
/\bdo\s+not\s+change\s+([^\n.]+)/i,
|
|
238
|
+
/\bmust\s+not\s+touch\s+([^\n.]+)/i,
|
|
239
|
+
/\bshould\s+not\s+touch\s+([^\n.]+)/i,
|
|
240
|
+
/\bwithout\s+changing\s+([^\n.]+)/i,
|
|
241
|
+
/\bwithout\s+touching\s+([^\n.]+)/i,
|
|
242
|
+
/\bno\s+changes\s+to\s+([^\n.]+)/i,
|
|
243
|
+
/\bkeep\s+([^\n.]+?)\s+(?:untouched|unchanged)\b/i,
|
|
244
|
+
/\bleave\s+([^\n.]+?)\s+untouched\b/i,
|
|
245
|
+
/\bavoid\s+changing\s+([^\n.]+)/i,
|
|
246
|
+
/\bexclude\s+([^\n.]+)/i,
|
|
247
|
+
/\bforbidden\s+([^\n.]+)/i
|
|
248
|
+
];
|
|
249
|
+
for (const re of explicitPhrases) {
|
|
250
|
+
addBoundaryList(extractScopePhrase(task, re));
|
|
251
|
+
}
|
|
220
252
|
return [...new Set(out)];
|
|
221
253
|
}
|
|
222
254
|
function extractExplicitPaths(task) {
|
|
@@ -448,11 +480,10 @@ var CATEGORY_KEYWORDS = [
|
|
|
448
480
|
];
|
|
449
481
|
function classifyTaskCategory(task, explicitPaths) {
|
|
450
482
|
const lower = task.toLowerCase();
|
|
451
|
-
if (CLI_SCOPE_RE.test(task)) return "cli";
|
|
452
483
|
const pathHints = explicitPaths.join(" ").toLowerCase();
|
|
453
484
|
for (const [category, keywords] of CATEGORY_KEYWORDS) {
|
|
454
485
|
const combined = lower + " " + pathHints;
|
|
455
|
-
if (keywords.some((kw) => combined
|
|
486
|
+
if (keywords.some((kw) => hasPositiveKeywordMention(combined, kw))) {
|
|
456
487
|
return category;
|
|
457
488
|
}
|
|
458
489
|
}
|
|
@@ -635,6 +666,28 @@ function buildCategoryScope(category, hasSrc, hasApp, hasPages) {
|
|
|
635
666
|
"Check no regression in adjacent routes"
|
|
636
667
|
]
|
|
637
668
|
};
|
|
669
|
+
case "docs":
|
|
670
|
+
return {
|
|
671
|
+
allowedHints: [
|
|
672
|
+
"README.md - project documentation",
|
|
673
|
+
"docs/ - documentation files",
|
|
674
|
+
"CHANGELOG.md or CONTRIBUTING.md if task-specific"
|
|
675
|
+
],
|
|
676
|
+
forbiddenAdditions: [
|
|
677
|
+
"Do not touch auth internals, session logic, or JWT handling",
|
|
678
|
+
"Do not touch billing, subscription, payment, or webhook logic",
|
|
679
|
+
"Do not touch database schema or migrations",
|
|
680
|
+
"Do not touch .env files or secrets"
|
|
681
|
+
],
|
|
682
|
+
stopRules: [
|
|
683
|
+
"Stop if the requested change requires code-path behavior changes outside docs",
|
|
684
|
+
"Stop if sensitive files or secrets are referenced"
|
|
685
|
+
],
|
|
686
|
+
verificationSteps: [
|
|
687
|
+
"Confirm documentation text matches the requested task",
|
|
688
|
+
"Check markdown formatting renders correctly"
|
|
689
|
+
]
|
|
690
|
+
};
|
|
638
691
|
default:
|
|
639
692
|
return {
|
|
640
693
|
allowedHints: [],
|
|
@@ -695,6 +748,28 @@ var LOOP_PATTERNS = [
|
|
|
695
748
|
/\b(keep (trying|going|working)|iterate until|loop until|retry)\b/i,
|
|
696
749
|
/\b(if it doesn.t work.{0,20}try again)\b/i
|
|
697
750
|
];
|
|
751
|
+
var NEGATION_PREFIX_RE2 = /\b(do not|don't|dont|never|avoid|must not|should not|without changing|without touching|no changes to|keep .* untouched|leave .* untouched|keep .* unchanged)\b/i;
|
|
752
|
+
function hasNegationNear2(text, index) {
|
|
753
|
+
const start = Math.max(0, index - 64);
|
|
754
|
+
const window = text.slice(start, index + 8);
|
|
755
|
+
return NEGATION_PREFIX_RE2.test(window);
|
|
756
|
+
}
|
|
757
|
+
function hasPositiveKeywordMention2(taskLower, keyword) {
|
|
758
|
+
let idx = taskLower.indexOf(keyword.toLowerCase());
|
|
759
|
+
while (idx !== -1) {
|
|
760
|
+
if (!hasNegationNear2(taskLower, idx)) return true;
|
|
761
|
+
idx = taskLower.indexOf(keyword.toLowerCase(), idx + keyword.length);
|
|
762
|
+
}
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
function hasNegatedKeywordMention(taskLower, keyword) {
|
|
766
|
+
let idx = taskLower.indexOf(keyword.toLowerCase());
|
|
767
|
+
while (idx !== -1) {
|
|
768
|
+
if (hasNegationNear2(taskLower, idx)) return true;
|
|
769
|
+
idx = taskLower.indexOf(keyword.toLowerCase(), idx + keyword.length);
|
|
770
|
+
}
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
698
773
|
function scoreTask(task, flags) {
|
|
699
774
|
let score = 100;
|
|
700
775
|
for (const flag of flags) {
|
|
@@ -755,7 +830,7 @@ function detectProjectContext(cwd = process.cwd()) {
|
|
|
755
830
|
function detectMegaRun(taskLower, task) {
|
|
756
831
|
const found = [];
|
|
757
832
|
for (const [system, keywords] of Object.entries(MEGA_RUN_SYSTEMS)) {
|
|
758
|
-
if (keywords.some((kw) => taskLower
|
|
833
|
+
if (keywords.some((kw) => hasPositiveKeywordMention2(taskLower, kw))) {
|
|
759
834
|
found.push(system);
|
|
760
835
|
}
|
|
761
836
|
}
|
|
@@ -766,17 +841,24 @@ function detectMegaRun(taskLower, task) {
|
|
|
766
841
|
function detectAreasTouched(taskLower) {
|
|
767
842
|
const forbidden = [];
|
|
768
843
|
const sensitive = [];
|
|
844
|
+
const boundaries = [];
|
|
769
845
|
for (const [area, keywords] of Object.entries(ALWAYS_FORBIDDEN_KEYWORDS)) {
|
|
770
|
-
if (keywords.some((kw) => taskLower
|
|
846
|
+
if (keywords.some((kw) => hasPositiveKeywordMention2(taskLower, kw))) {
|
|
771
847
|
forbidden.push(area);
|
|
772
848
|
}
|
|
849
|
+
if (keywords.some((kw) => hasNegatedKeywordMention(taskLower, kw))) {
|
|
850
|
+
boundaries.push(area);
|
|
851
|
+
}
|
|
773
852
|
}
|
|
774
853
|
for (const [area, keywords] of Object.entries(SENSITIVE_BILLING_KEYWORDS)) {
|
|
775
|
-
if (keywords.some((kw) => taskLower
|
|
854
|
+
if (keywords.some((kw) => hasPositiveKeywordMention2(taskLower, kw))) {
|
|
776
855
|
sensitive.push(area);
|
|
777
856
|
}
|
|
857
|
+
if (keywords.some((kw) => hasNegatedKeywordMention(taskLower, kw))) {
|
|
858
|
+
boundaries.push(area);
|
|
859
|
+
}
|
|
778
860
|
}
|
|
779
|
-
return { forbidden, sensitive };
|
|
861
|
+
return { forbidden, sensitive, boundaries: [...new Set(boundaries)] };
|
|
780
862
|
}
|
|
781
863
|
function auditTask(task, config, cwd = process.cwd()) {
|
|
782
864
|
const flags = [];
|
|
@@ -866,7 +948,7 @@ function auditTask(task, config, cwd = process.cwd()) {
|
|
|
866
948
|
detail: "References to full context or entire conversation force expensive context loading."
|
|
867
949
|
});
|
|
868
950
|
}
|
|
869
|
-
const { forbidden: forbiddenAreasTouched, sensitive: sensitiveAreasRelevant } = detectAreasTouched(taskLower);
|
|
951
|
+
const { forbidden: forbiddenAreasTouched, sensitive: sensitiveAreasRelevant, boundaries } = detectAreasTouched(taskLower);
|
|
870
952
|
if (forbiddenAreasTouched.length > 0) {
|
|
871
953
|
flags.push({
|
|
872
954
|
code: "touches_forbidden_area",
|
|
@@ -883,6 +965,14 @@ function auditTask(task, config, cwd = process.cwd()) {
|
|
|
883
965
|
detail: `Task touches ${sensitiveAreasRelevant.join(", ")}. These are moved to SENSITIVE SCOPE: inspect allowed, editing requires explicit approval.`
|
|
884
966
|
});
|
|
885
967
|
}
|
|
968
|
+
if (boundaries.length > 0) {
|
|
969
|
+
flags.push({
|
|
970
|
+
code: "forbidden_boundaries_detected",
|
|
971
|
+
label: `Boundaries detected: ${boundaries.join(", ")}`,
|
|
972
|
+
severity: "info",
|
|
973
|
+
detail: "Sensitive systems in negated constraints are treated as forbidden boundaries, not active task scope."
|
|
974
|
+
});
|
|
975
|
+
}
|
|
886
976
|
const isSimpleTask = task.length < 80 && flags.filter((f) => f.severity === "critical").length === 0;
|
|
887
977
|
if (isSimpleTask && config.defaultModel === "opus") {
|
|
888
978
|
flags.push({
|
|
@@ -960,6 +1050,10 @@ function scoreToRisk2(score) {
|
|
|
960
1050
|
}
|
|
961
1051
|
function cleanObjective(task) {
|
|
962
1052
|
let t = task.trim();
|
|
1053
|
+
t = t.replace(
|
|
1054
|
+
/(?:^|[\s,.])(do not|don't|dont|must not|should not|without changing|without touching|no changes to|keep .*? unchanged|keep .*? untouched|leave .*? untouched|avoid changing)\b[^.]*\.?/gi,
|
|
1055
|
+
" "
|
|
1056
|
+
);
|
|
963
1057
|
t = t.replace(/,?\s*check everything(\s+and\b)?/gi, "");
|
|
964
1058
|
t = t.replace(/,?\s*(look|search|scan)\s+everywhere(\s+and\b)?/gi, "");
|
|
965
1059
|
t = t.replace(/,?\s*review everything(\s+and\b)?/gi, "");
|
|
@@ -2505,7 +2599,7 @@ function buildSyncPayload(input) {
|
|
|
2505
2599
|
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
|
|
2506
2600
|
const { cwd, projectName, config, projectAudit, memoryMarkdown, runs } = input;
|
|
2507
2601
|
const latest = runs[0];
|
|
2508
|
-
const
|
|
2602
|
+
const nowIso2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
2509
2603
|
const localProjectId = buildLocalProjectId(cwd);
|
|
2510
2604
|
const latestPromptText = readTextFileIfExists(path6.join(cwd, ".runtrim", "latest-prompt.md"));
|
|
2511
2605
|
const continuationPromptText = readTextFileIfExists(
|
|
@@ -2567,7 +2661,7 @@ function buildSyncPayload(input) {
|
|
|
2567
2661
|
name: resolveProjectName2(cwd, projectName, projectAudit == null ? void 0 : projectAudit.projectName),
|
|
2568
2662
|
stack: (_a2 = projectAudit == null ? void 0 : projectAudit.detectedStack) != null ? _a2 : config.stack ? config.stack.split(",").map((s) => s.trim()).filter(Boolean) : ["auto"],
|
|
2569
2663
|
packageManager: (_c = (_b = projectAudit == null ? void 0 : projectAudit.packageManager) != null ? _b : config.packageManager) != null ? _c : null,
|
|
2570
|
-
lastUpdated:
|
|
2664
|
+
lastUpdated: nowIso2
|
|
2571
2665
|
},
|
|
2572
2666
|
memory: {
|
|
2573
2667
|
markdown: memoryMarkdown,
|
|
@@ -2649,7 +2743,8 @@ function writeCanonicalRuntrimMd(cwd = process.cwd(), projectName) {
|
|
|
2649
2743
|
"## How to start an AI coding task",
|
|
2650
2744
|
"",
|
|
2651
2745
|
"```",
|
|
2652
|
-
|
|
2746
|
+
"runtrim start",
|
|
2747
|
+
'runtrim agent "Your task" --copy',
|
|
2653
2748
|
"```",
|
|
2654
2749
|
"",
|
|
2655
2750
|
"RunTrim creates a scoped contract, loads project memory, and generates a guarded prompt.",
|
|
@@ -2670,7 +2765,7 @@ function writeCanonicalRuntrimMd(cwd = process.cwd(), projectName) {
|
|
|
2670
2765
|
"",
|
|
2671
2766
|
"1. Read `.runtrim/contracts/latest.md`.",
|
|
2672
2767
|
" - If `Status: active` \u2014 a live task exists. Follow the contract strictly.",
|
|
2673
|
-
' - If `Status: none` \u2014 no active task. Ask the user to run `runtrim
|
|
2768
|
+
' - If `Status: none` \u2014 no active task. Ask the user to run `runtrim start` then `runtrim agent "Your task" --copy`.',
|
|
2674
2769
|
"2. Do not assume any prior task is still active.",
|
|
2675
2770
|
"3. Stay inside the allowed scope defined in the contract.",
|
|
2676
2771
|
"4. Stop and ask before touching any forbidden area.",
|
|
@@ -2695,7 +2790,8 @@ function writeRestingContract(cwd = process.cwd()) {
|
|
|
2695
2790
|
"Start one with:",
|
|
2696
2791
|
"",
|
|
2697
2792
|
"```",
|
|
2698
|
-
|
|
2793
|
+
"runtrim start",
|
|
2794
|
+
'runtrim agent "Your task" --copy',
|
|
2699
2795
|
"```",
|
|
2700
2796
|
"",
|
|
2701
2797
|
"---",
|
|
@@ -2718,7 +2814,8 @@ function writeRestingMemory(cwd = process.cwd()) {
|
|
|
2718
2814
|
"Start a new session with:",
|
|
2719
2815
|
"",
|
|
2720
2816
|
"```",
|
|
2721
|
-
|
|
2817
|
+
"runtrim start",
|
|
2818
|
+
'runtrim agent "Your task" --copy',
|
|
2722
2819
|
"```",
|
|
2723
2820
|
"",
|
|
2724
2821
|
"---",
|
|
@@ -2839,7 +2936,7 @@ function writeBridgeInstructions(cwd = process.cwd()) {
|
|
|
2839
2936
|
"1. Read `RUNTRIM.md`.",
|
|
2840
2937
|
"2. Read `.runtrim/contracts/latest.md`.",
|
|
2841
2938
|
" - If `Status: active` \u2014 follow the contract strictly.",
|
|
2842
|
-
' - If `Status: none` \u2014 stop. Ask the user to run `runtrim
|
|
2939
|
+
' - If `Status: none` \u2014 stop. Ask the user to run `runtrim start` then `runtrim agent "Your task" --copy`.',
|
|
2843
2940
|
"3. If the contract is active, read `.runtrim/memory/current.md` for session context.",
|
|
2844
2941
|
" If no active session, read `.runtrim/memory/baseline.md` for project baseline.",
|
|
2845
2942
|
"",
|
|
@@ -2913,6 +3010,28 @@ function buildBridgePrompt(contractText, ctx) {
|
|
|
2913
3010
|
// src/lib/run-watch.ts
|
|
2914
3011
|
function normalizeScopeKeywords2(scope) {
|
|
2915
3012
|
var _a2;
|
|
3013
|
+
const genericStopwords = /* @__PURE__ */ new Set([
|
|
3014
|
+
"read",
|
|
3015
|
+
"write",
|
|
3016
|
+
"reference",
|
|
3017
|
+
"touch",
|
|
3018
|
+
"modify",
|
|
3019
|
+
"change",
|
|
3020
|
+
"update",
|
|
3021
|
+
"allow",
|
|
3022
|
+
"scope",
|
|
3023
|
+
"paths",
|
|
3024
|
+
"path",
|
|
3025
|
+
"files",
|
|
3026
|
+
"file",
|
|
3027
|
+
"only",
|
|
3028
|
+
"with",
|
|
3029
|
+
"without",
|
|
3030
|
+
"before",
|
|
3031
|
+
"after",
|
|
3032
|
+
"inside",
|
|
3033
|
+
"outside"
|
|
3034
|
+
]);
|
|
2916
3035
|
const words = /* @__PURE__ */ new Set();
|
|
2917
3036
|
for (const line of scope) {
|
|
2918
3037
|
const lower = line.toLowerCase();
|
|
@@ -2922,7 +3041,7 @@ function normalizeScopeKeywords2(scope) {
|
|
|
2922
3041
|
}
|
|
2923
3042
|
const cleaned = lower.replace(/[^a-z0-9_./\s-]/g, " ").split(/\s+/).filter(Boolean);
|
|
2924
3043
|
for (const token of cleaned) {
|
|
2925
|
-
if (token.length >= 4) words.add(token);
|
|
3044
|
+
if (token.length >= 4 && !genericStopwords.has(token)) words.add(token);
|
|
2926
3045
|
}
|
|
2927
3046
|
}
|
|
2928
3047
|
return [...words];
|
|
@@ -3019,15 +3138,7 @@ import fs8 from "fs";
|
|
|
3019
3138
|
import os from "os";
|
|
3020
3139
|
import path8 from "path";
|
|
3021
3140
|
import { execa as execa2 } from "execa";
|
|
3022
|
-
var
|
|
3023
|
-
version: 1,
|
|
3024
|
-
plan: "free",
|
|
3025
|
-
trackedRepos: [],
|
|
3026
|
-
telemetry: {
|
|
3027
|
-
enabled: false,
|
|
3028
|
-
anonymousId: ""
|
|
3029
|
-
}
|
|
3030
|
-
};
|
|
3141
|
+
var EMPTY_TELEMETRY = { enabled: false, anonymousId: "" };
|
|
3031
3142
|
function normalizeRepoPath(input) {
|
|
3032
3143
|
const resolved = path8.resolve(input);
|
|
3033
3144
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
@@ -3035,41 +3146,208 @@ function normalizeRepoPath(input) {
|
|
|
3035
3146
|
function hashValue(value) {
|
|
3036
3147
|
return crypto.createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
3037
3148
|
}
|
|
3149
|
+
function nowIso() {
|
|
3150
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3151
|
+
}
|
|
3152
|
+
function randomId(prefix) {
|
|
3153
|
+
return `${prefix}_${crypto.randomBytes(12).toString("hex")}`;
|
|
3154
|
+
}
|
|
3038
3155
|
function getGlobalRunTrimDir() {
|
|
3039
3156
|
return path8.join(os.homedir(), ".runtrim");
|
|
3040
3157
|
}
|
|
3041
3158
|
function getGlobalRegistryPath() {
|
|
3042
3159
|
return path8.join(getGlobalRunTrimDir(), "global.json");
|
|
3043
3160
|
}
|
|
3044
|
-
function
|
|
3161
|
+
function getInstallStatePath() {
|
|
3162
|
+
return path8.join(getGlobalRunTrimDir(), "install-state.json");
|
|
3163
|
+
}
|
|
3164
|
+
function buildSealInput(registry) {
|
|
3165
|
+
const tracked = [...registry.trackedRepos].map((r) => ({
|
|
3166
|
+
id: r.id,
|
|
3167
|
+
name: r.name,
|
|
3168
|
+
path: normalizeRepoPath(r.path),
|
|
3169
|
+
gitRemote: r.gitRemote,
|
|
3170
|
+
createdAt: r.createdAt,
|
|
3171
|
+
lastSeenAt: r.lastSeenAt
|
|
3172
|
+
})).sort((a, b) => `${a.id}:${a.path}`.localeCompare(`${b.id}:${b.path}`));
|
|
3173
|
+
const payload = {
|
|
3174
|
+
version: registry.version,
|
|
3175
|
+
stateVersion: registry.stateVersion,
|
|
3176
|
+
plan: registry.plan,
|
|
3177
|
+
machineInstallId: registry.machineInstallId,
|
|
3178
|
+
createdAt: registry.createdAt,
|
|
3179
|
+
updatedAt: registry.updatedAt,
|
|
3180
|
+
trackedRepos: tracked,
|
|
3181
|
+
lastKnownRepo: registry.lastKnownRepo ? __spreadProps(__spreadValues({}, registry.lastKnownRepo), {
|
|
3182
|
+
path: normalizeRepoPath(registry.lastKnownRepo.path)
|
|
3183
|
+
}) : null
|
|
3184
|
+
};
|
|
3185
|
+
return JSON.stringify(payload);
|
|
3186
|
+
}
|
|
3187
|
+
function computeSeal(registry) {
|
|
3188
|
+
return crypto.createHash("sha256").update(buildSealInput(registry)).digest("hex");
|
|
3189
|
+
}
|
|
3190
|
+
function sanitizeTrackedRepoEntry(input) {
|
|
3191
|
+
var _a2, _b, _c, _d, _e, _f;
|
|
3192
|
+
const id = String((_a2 = input.id) != null ? _a2 : "").trim();
|
|
3193
|
+
const rawPath = String((_b = input.path) != null ? _b : "").trim();
|
|
3194
|
+
if (!id || !rawPath) return null;
|
|
3195
|
+
return {
|
|
3196
|
+
id,
|
|
3197
|
+
name: String((_c = input.name) != null ? _c : "").trim(),
|
|
3198
|
+
path: normalizeRepoPath(rawPath),
|
|
3199
|
+
gitRemote: String((_d = input.gitRemote) != null ? _d : "").trim(),
|
|
3200
|
+
createdAt: String((_e = input.createdAt) != null ? _e : "").trim(),
|
|
3201
|
+
lastSeenAt: String((_f = input.lastSeenAt) != null ? _f : "").trim()
|
|
3202
|
+
};
|
|
3203
|
+
}
|
|
3204
|
+
function readInstallStateRaw() {
|
|
3205
|
+
var _a2, _b, _c;
|
|
3206
|
+
const p = getInstallStatePath();
|
|
3207
|
+
if (!fs8.existsSync(p)) return { exists: false, state: null };
|
|
3208
|
+
try {
|
|
3209
|
+
const parsed = JSON.parse(fs8.readFileSync(p, "utf-8"));
|
|
3210
|
+
const machineInstallId = String((_a2 = parsed.machineInstallId) != null ? _a2 : "").trim();
|
|
3211
|
+
if (!machineInstallId) return { exists: true, state: null };
|
|
3212
|
+
return {
|
|
3213
|
+
exists: true,
|
|
3214
|
+
state: {
|
|
3215
|
+
machineInstallId,
|
|
3216
|
+
createdAt: String((_b = parsed.createdAt) != null ? _b : "").trim() || nowIso(),
|
|
3217
|
+
updatedAt: String((_c = parsed.updatedAt) != null ? _c : "").trim() || nowIso()
|
|
3218
|
+
}
|
|
3219
|
+
};
|
|
3220
|
+
} catch (e) {
|
|
3221
|
+
return { exists: true, state: null };
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
function writeInstallState(state) {
|
|
3225
|
+
const dir = getGlobalRunTrimDir();
|
|
3226
|
+
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
3227
|
+
fs8.writeFileSync(getInstallStatePath(), JSON.stringify(state, null, 2), "utf-8");
|
|
3228
|
+
}
|
|
3229
|
+
function ensureInstallState() {
|
|
3230
|
+
const raw = readInstallStateRaw();
|
|
3231
|
+
if (raw.exists && raw.state) return raw.state;
|
|
3232
|
+
const created = {
|
|
3233
|
+
machineInstallId: randomId("rt_install"),
|
|
3234
|
+
createdAt: nowIso(),
|
|
3235
|
+
updatedAt: nowIso()
|
|
3236
|
+
};
|
|
3237
|
+
writeInstallState(created);
|
|
3238
|
+
return created;
|
|
3239
|
+
}
|
|
3240
|
+
function buildDefaultRegistry(install) {
|
|
3241
|
+
const base = {
|
|
3242
|
+
version: 2,
|
|
3243
|
+
stateVersion: 2,
|
|
3244
|
+
plan: "free",
|
|
3245
|
+
machineInstallId: install.machineInstallId,
|
|
3246
|
+
createdAt: nowIso(),
|
|
3247
|
+
updatedAt: nowIso(),
|
|
3248
|
+
trackedRepos: [],
|
|
3249
|
+
lastKnownRepo: null,
|
|
3250
|
+
telemetry: __spreadValues({}, EMPTY_TELEMETRY)
|
|
3251
|
+
};
|
|
3252
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3253
|
+
integrity: {
|
|
3254
|
+
algorithm: "sha256-local-seal-v1",
|
|
3255
|
+
seal: computeSeal(base)
|
|
3256
|
+
}
|
|
3257
|
+
});
|
|
3258
|
+
}
|
|
3259
|
+
function saveRegistryWithSeal(registry) {
|
|
3260
|
+
var _a2;
|
|
3261
|
+
const dir = getGlobalRunTrimDir();
|
|
3262
|
+
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
3263
|
+
const normalizedBase = __spreadProps(__spreadValues({}, registry), {
|
|
3264
|
+
version: 2,
|
|
3265
|
+
stateVersion: 2,
|
|
3266
|
+
trackedRepos: registry.trackedRepos.map((r) => __spreadProps(__spreadValues({}, r), { path: normalizeRepoPath(r.path) })),
|
|
3267
|
+
telemetry: (_a2 = registry.telemetry) != null ? _a2 : __spreadValues({}, EMPTY_TELEMETRY)
|
|
3268
|
+
});
|
|
3269
|
+
const sealed = __spreadProps(__spreadValues({}, normalizedBase), {
|
|
3270
|
+
integrity: {
|
|
3271
|
+
algorithm: "sha256-local-seal-v1",
|
|
3272
|
+
seal: computeSeal(normalizedBase)
|
|
3273
|
+
}
|
|
3274
|
+
});
|
|
3275
|
+
fs8.writeFileSync(getGlobalRegistryPath(), JSON.stringify(sealed, null, 2), "utf-8");
|
|
3276
|
+
}
|
|
3277
|
+
function inspectGlobalRegistry() {
|
|
3278
|
+
var _a2, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
3279
|
+
const installRaw = readInstallStateRaw();
|
|
3280
|
+
const install = (_a2 = installRaw.state) != null ? _a2 : ensureInstallState();
|
|
3045
3281
|
const registryPath = getGlobalRegistryPath();
|
|
3046
|
-
|
|
3282
|
+
const defaultRegistry = buildDefaultRegistry(install);
|
|
3283
|
+
if (!fs8.existsSync(registryPath)) {
|
|
3284
|
+
if (installRaw.exists) {
|
|
3285
|
+
return {
|
|
3286
|
+
registry: defaultRegistry,
|
|
3287
|
+
needsRepair: true,
|
|
3288
|
+
repairReason: "missing_registry_after_initialization"
|
|
3289
|
+
};
|
|
3290
|
+
}
|
|
3291
|
+
return { registry: defaultRegistry, needsRepair: false, repairReason: null };
|
|
3292
|
+
}
|
|
3047
3293
|
try {
|
|
3048
3294
|
const raw = JSON.parse(fs8.readFileSync(registryPath, "utf-8"));
|
|
3049
|
-
|
|
3050
|
-
|
|
3295
|
+
const trackedRepos = Array.isArray(raw.trackedRepos) ? raw.trackedRepos.map((item) => sanitizeTrackedRepoEntry(item)).filter((item) => Boolean(item)) : [];
|
|
3296
|
+
const base = {
|
|
3297
|
+
version: 2,
|
|
3298
|
+
stateVersion: 2,
|
|
3051
3299
|
plan: raw.plan === "free" ? "free" : "free",
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3300
|
+
machineInstallId: String((_b = raw.machineInstallId) != null ? _b : "").trim() || install.machineInstallId,
|
|
3301
|
+
createdAt: String((_c = raw.createdAt) != null ? _c : "").trim() || nowIso(),
|
|
3302
|
+
updatedAt: String((_d = raw.updatedAt) != null ? _d : "").trim() || nowIso(),
|
|
3303
|
+
trackedRepos,
|
|
3304
|
+
lastKnownRepo: raw.lastKnownRepo && typeof raw.lastKnownRepo === "object" ? {
|
|
3305
|
+
id: String((_e = raw.lastKnownRepo.id) != null ? _e : "").trim(),
|
|
3306
|
+
name: String((_f = raw.lastKnownRepo.name) != null ? _f : "").trim(),
|
|
3307
|
+
path: normalizeRepoPath(String((_g = raw.lastKnownRepo.path) != null ? _g : "")),
|
|
3308
|
+
gitRemote: String((_h = raw.lastKnownRepo.gitRemote) != null ? _h : "").trim(),
|
|
3309
|
+
lastSeenAt: String((_i = raw.lastKnownRepo.lastSeenAt) != null ? _i : "").trim() || nowIso()
|
|
3310
|
+
} : null,
|
|
3060
3311
|
telemetry: {
|
|
3061
3312
|
enabled: typeof raw.telemetry === "object" && raw.telemetry !== null && Boolean(raw.telemetry.enabled),
|
|
3062
3313
|
anonymousId: typeof raw.telemetry === "object" && raw.telemetry !== null && typeof raw.telemetry.anonymousId === "string" ? String(raw.telemetry.anonymousId).slice(0, 120) : ""
|
|
3063
3314
|
}
|
|
3064
3315
|
};
|
|
3316
|
+
const normalized = __spreadProps(__spreadValues({}, base), {
|
|
3317
|
+
integrity: {
|
|
3318
|
+
algorithm: "sha256-local-seal-v1",
|
|
3319
|
+
seal: raw.integrity && typeof raw.integrity === "object" && typeof raw.integrity.seal === "string" ? String(raw.integrity.seal) : ""
|
|
3320
|
+
}
|
|
3321
|
+
});
|
|
3322
|
+
if (normalized.machineInstallId !== install.machineInstallId) {
|
|
3323
|
+
return {
|
|
3324
|
+
registry: normalized,
|
|
3325
|
+
needsRepair: true,
|
|
3326
|
+
repairReason: "machine_install_id_mismatch"
|
|
3327
|
+
};
|
|
3328
|
+
}
|
|
3329
|
+
const expectedSeal = computeSeal(base);
|
|
3330
|
+
if (!normalized.integrity.seal || normalized.integrity.seal !== expectedSeal) {
|
|
3331
|
+
return {
|
|
3332
|
+
registry: normalized,
|
|
3333
|
+
needsRepair: true,
|
|
3334
|
+
repairReason: "integrity_seal_mismatch"
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
return { registry: normalized, needsRepair: false, repairReason: null };
|
|
3065
3338
|
} catch (e) {
|
|
3066
|
-
return
|
|
3339
|
+
return {
|
|
3340
|
+
registry: defaultRegistry,
|
|
3341
|
+
needsRepair: true,
|
|
3342
|
+
repairReason: "registry_corrupt"
|
|
3343
|
+
};
|
|
3067
3344
|
}
|
|
3068
3345
|
}
|
|
3346
|
+
function loadGlobalRegistry() {
|
|
3347
|
+
return inspectGlobalRegistry().registry;
|
|
3348
|
+
}
|
|
3069
3349
|
function saveGlobalRegistry(registry) {
|
|
3070
|
-
|
|
3071
|
-
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
3072
|
-
fs8.writeFileSync(getGlobalRegistryPath(), JSON.stringify(registry, null, 2), "utf-8");
|
|
3350
|
+
saveRegistryWithSeal(registry);
|
|
3073
3351
|
}
|
|
3074
3352
|
async function getCurrentRepoIdentity(cwd = process.cwd()) {
|
|
3075
3353
|
const normalizedPath = normalizeRepoPath(cwd);
|
|
@@ -3099,36 +3377,71 @@ function findTrackedRepo(trackedRepos, currentRepo) {
|
|
|
3099
3377
|
return byPath != null ? byPath : null;
|
|
3100
3378
|
}
|
|
3101
3379
|
async function assertFreeRepoAllowed(cwd = process.cwd()) {
|
|
3102
|
-
const
|
|
3380
|
+
const inspected = inspectGlobalRegistry();
|
|
3381
|
+
const registry = inspected.registry;
|
|
3103
3382
|
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3104
3383
|
const trackedRepo = findTrackedRepo(registry.trackedRepos, currentRepo);
|
|
3105
|
-
|
|
3106
|
-
|
|
3384
|
+
const base = {
|
|
3385
|
+
plan: registry.plan,
|
|
3386
|
+
currentRepo,
|
|
3387
|
+
trackedRepo,
|
|
3388
|
+
registryPath: getGlobalRegistryPath()
|
|
3389
|
+
};
|
|
3390
|
+
if (inspected.needsRepair) {
|
|
3391
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3392
|
+
allowed: false,
|
|
3393
|
+
status: "blocked_repair",
|
|
3394
|
+
repairRequired: true,
|
|
3395
|
+
repairReason: inspected.repairReason
|
|
3396
|
+
});
|
|
3107
3397
|
}
|
|
3108
|
-
if (
|
|
3109
|
-
return {
|
|
3398
|
+
if (registry.plan !== "free") {
|
|
3399
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3400
|
+
allowed: true,
|
|
3401
|
+
status: "allowed",
|
|
3402
|
+
repairRequired: false,
|
|
3403
|
+
repairReason: null
|
|
3404
|
+
});
|
|
3110
3405
|
}
|
|
3111
|
-
if (registry.trackedRepos.length === 0) {
|
|
3112
|
-
return {
|
|
3406
|
+
if (trackedRepo || registry.trackedRepos.length === 0) {
|
|
3407
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3408
|
+
allowed: true,
|
|
3409
|
+
status: "allowed",
|
|
3410
|
+
repairRequired: false,
|
|
3411
|
+
repairReason: null
|
|
3412
|
+
});
|
|
3113
3413
|
}
|
|
3114
|
-
return {
|
|
3414
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3115
3415
|
allowed: false,
|
|
3116
|
-
|
|
3117
|
-
|
|
3416
|
+
status: "blocked_limit",
|
|
3417
|
+
repairRequired: false,
|
|
3418
|
+
repairReason: null,
|
|
3118
3419
|
trackedRepo: registry.trackedRepos[0]
|
|
3119
|
-
};
|
|
3420
|
+
});
|
|
3120
3421
|
}
|
|
3121
3422
|
async function registerCurrentRepo(cwd = process.cwd()) {
|
|
3423
|
+
const check = await assertFreeRepoAllowed(cwd);
|
|
3424
|
+
if (!check.allowed && check.status === "blocked_repair") {
|
|
3425
|
+
throw new Error("runtrim_local_state_repair_required");
|
|
3426
|
+
}
|
|
3122
3427
|
const registry = loadGlobalRegistry();
|
|
3123
3428
|
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3124
|
-
const now = (
|
|
3429
|
+
const now = nowIso();
|
|
3125
3430
|
const existing = findTrackedRepo(registry.trackedRepos, currentRepo);
|
|
3126
3431
|
if (existing) {
|
|
3127
3432
|
existing.lastSeenAt = now;
|
|
3128
3433
|
existing.name = currentRepo.name;
|
|
3129
3434
|
existing.path = currentRepo.path;
|
|
3130
3435
|
existing.gitRemote = currentRepo.gitRemote;
|
|
3131
|
-
|
|
3436
|
+
registry.updatedAt = now;
|
|
3437
|
+
registry.lastKnownRepo = {
|
|
3438
|
+
id: currentRepo.id,
|
|
3439
|
+
name: currentRepo.name,
|
|
3440
|
+
path: currentRepo.path,
|
|
3441
|
+
gitRemote: currentRepo.gitRemote,
|
|
3442
|
+
lastSeenAt: now
|
|
3443
|
+
};
|
|
3444
|
+
saveRegistryWithSeal(registry);
|
|
3132
3445
|
return existing;
|
|
3133
3446
|
}
|
|
3134
3447
|
const entry = {
|
|
@@ -3139,24 +3452,110 @@ async function registerCurrentRepo(cwd = process.cwd()) {
|
|
|
3139
3452
|
createdAt: now,
|
|
3140
3453
|
lastSeenAt: now
|
|
3141
3454
|
};
|
|
3142
|
-
registry.trackedRepos
|
|
3143
|
-
|
|
3455
|
+
registry.trackedRepos = [entry];
|
|
3456
|
+
registry.updatedAt = now;
|
|
3457
|
+
registry.lastKnownRepo = {
|
|
3458
|
+
id: entry.id,
|
|
3459
|
+
name: entry.name,
|
|
3460
|
+
path: entry.path,
|
|
3461
|
+
gitRemote: entry.gitRemote,
|
|
3462
|
+
lastSeenAt: now
|
|
3463
|
+
};
|
|
3464
|
+
saveRegistryWithSeal(registry);
|
|
3144
3465
|
return entry;
|
|
3145
3466
|
}
|
|
3467
|
+
async function repairGlobalRegistry(cwd = process.cwd(), options = {}) {
|
|
3468
|
+
const before = await assertFreeRepoAllowed(cwd);
|
|
3469
|
+
if (!before.repairRequired) {
|
|
3470
|
+
return { repaired: false, check: before };
|
|
3471
|
+
}
|
|
3472
|
+
const install = ensureInstallState();
|
|
3473
|
+
const now = nowIso();
|
|
3474
|
+
const repaired = buildDefaultRegistry(install);
|
|
3475
|
+
repaired.createdAt = now;
|
|
3476
|
+
repaired.updatedAt = now;
|
|
3477
|
+
if (options.useCurrentRepo) {
|
|
3478
|
+
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3479
|
+
repaired.trackedRepos = [
|
|
3480
|
+
{
|
|
3481
|
+
id: currentRepo.id,
|
|
3482
|
+
name: currentRepo.name,
|
|
3483
|
+
path: currentRepo.path,
|
|
3484
|
+
gitRemote: currentRepo.gitRemote,
|
|
3485
|
+
createdAt: now,
|
|
3486
|
+
lastSeenAt: now
|
|
3487
|
+
}
|
|
3488
|
+
];
|
|
3489
|
+
repaired.lastKnownRepo = {
|
|
3490
|
+
id: currentRepo.id,
|
|
3491
|
+
name: currentRepo.name,
|
|
3492
|
+
path: currentRepo.path,
|
|
3493
|
+
gitRemote: currentRepo.gitRemote,
|
|
3494
|
+
lastSeenAt: now
|
|
3495
|
+
};
|
|
3496
|
+
}
|
|
3497
|
+
saveRegistryWithSeal(repaired);
|
|
3498
|
+
const check = await assertFreeRepoAllowed(cwd);
|
|
3499
|
+
return { repaired: true, check };
|
|
3500
|
+
}
|
|
3146
3501
|
async function unlinkCurrentRepo(cwd = process.cwd(), force = false) {
|
|
3147
3502
|
var _a2;
|
|
3503
|
+
const check = await assertFreeRepoAllowed(cwd);
|
|
3504
|
+
if (check.status === "blocked_repair") {
|
|
3505
|
+
if (!force) {
|
|
3506
|
+
return {
|
|
3507
|
+
removed: false,
|
|
3508
|
+
forced: false,
|
|
3509
|
+
currentRepo: check.currentRepo,
|
|
3510
|
+
trackedRepo: null
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
const install = ensureInstallState();
|
|
3514
|
+
const repaired = buildDefaultRegistry(install);
|
|
3515
|
+
repaired.updatedAt = nowIso();
|
|
3516
|
+
repaired.lastKnownRepo = {
|
|
3517
|
+
id: check.currentRepo.id,
|
|
3518
|
+
name: check.currentRepo.name,
|
|
3519
|
+
path: check.currentRepo.path,
|
|
3520
|
+
gitRemote: check.currentRepo.gitRemote,
|
|
3521
|
+
lastSeenAt: repaired.updatedAt
|
|
3522
|
+
};
|
|
3523
|
+
saveRegistryWithSeal(repaired);
|
|
3524
|
+
return {
|
|
3525
|
+
removed: true,
|
|
3526
|
+
forced: true,
|
|
3527
|
+
currentRepo: check.currentRepo,
|
|
3528
|
+
trackedRepo: null
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
3148
3531
|
const registry = loadGlobalRegistry();
|
|
3149
3532
|
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3150
3533
|
const trackedRepo = findTrackedRepo(registry.trackedRepos, currentRepo);
|
|
3151
3534
|
if (trackedRepo) {
|
|
3152
3535
|
registry.trackedRepos = registry.trackedRepos.filter((repo) => repo.id !== trackedRepo.id);
|
|
3153
|
-
|
|
3536
|
+
registry.updatedAt = nowIso();
|
|
3537
|
+
registry.lastKnownRepo = {
|
|
3538
|
+
id: trackedRepo.id,
|
|
3539
|
+
name: trackedRepo.name,
|
|
3540
|
+
path: trackedRepo.path,
|
|
3541
|
+
gitRemote: trackedRepo.gitRemote,
|
|
3542
|
+
lastSeenAt: registry.updatedAt
|
|
3543
|
+
};
|
|
3544
|
+
saveRegistryWithSeal(registry);
|
|
3154
3545
|
return { removed: true, forced: false, currentRepo, trackedRepo };
|
|
3155
3546
|
}
|
|
3156
3547
|
if (force && registry.trackedRepos.length > 0) {
|
|
3157
3548
|
const first = registry.trackedRepos[0];
|
|
3158
3549
|
registry.trackedRepos = [];
|
|
3159
|
-
|
|
3550
|
+
registry.updatedAt = nowIso();
|
|
3551
|
+
registry.lastKnownRepo = {
|
|
3552
|
+
id: first.id,
|
|
3553
|
+
name: first.name,
|
|
3554
|
+
path: first.path,
|
|
3555
|
+
gitRemote: first.gitRemote,
|
|
3556
|
+
lastSeenAt: registry.updatedAt
|
|
3557
|
+
};
|
|
3558
|
+
saveRegistryWithSeal(registry);
|
|
3160
3559
|
return { removed: true, forced: true, currentRepo, trackedRepo: first };
|
|
3161
3560
|
}
|
|
3162
3561
|
return {
|
|
@@ -4640,21 +5039,21 @@ var MEDIUM_PATH_PATTERNS = [
|
|
|
4640
5039
|
];
|
|
4641
5040
|
function classifyFileRisk(files) {
|
|
4642
5041
|
if (files.length === 0) return "low";
|
|
4643
|
-
let
|
|
5042
|
+
let maxRisk2 = "low";
|
|
4644
5043
|
for (const f of files) {
|
|
4645
5044
|
const norm = f.replace(/\\/g, "/").toLowerCase();
|
|
4646
5045
|
if (CRITICAL_PATH_PATTERNS.some((p) => norm.includes(p))) {
|
|
4647
5046
|
return "critical";
|
|
4648
5047
|
}
|
|
4649
|
-
if (HIGH_PATH_PATTERNS.some((p) => norm.includes(p)) &&
|
|
4650
|
-
|
|
5048
|
+
if (HIGH_PATH_PATTERNS.some((p) => norm.includes(p)) && maxRisk2 !== "high") {
|
|
5049
|
+
maxRisk2 = "high";
|
|
4651
5050
|
continue;
|
|
4652
5051
|
}
|
|
4653
|
-
if (MEDIUM_PATH_PATTERNS.some((p) => norm.includes(p)) &&
|
|
4654
|
-
|
|
5052
|
+
if (MEDIUM_PATH_PATTERNS.some((p) => norm.includes(p)) && maxRisk2 === "low") {
|
|
5053
|
+
maxRisk2 = "medium";
|
|
4655
5054
|
}
|
|
4656
5055
|
}
|
|
4657
|
-
return
|
|
5056
|
+
return maxRisk2;
|
|
4658
5057
|
}
|
|
4659
5058
|
function isSensitivePath(filePath) {
|
|
4660
5059
|
const norm = filePath.replace(/\\/g, "/").toLowerCase();
|
|
@@ -4997,6 +5396,24 @@ function getLearningContext(cwd, task, runs) {
|
|
|
4997
5396
|
// src/lib/run-planner.ts
|
|
4998
5397
|
var FAST_PATH_CATEGORIES = /* @__PURE__ */ new Set(["ui", "docs", "tests", "unknown"]);
|
|
4999
5398
|
var ALWAYS_CONTRACT_CATEGORIES = /* @__PURE__ */ new Set(["auth", "billing", "payment", "webhook", "database", "env", "middleware"]);
|
|
5399
|
+
var RISK_ORDER = ["low", "medium", "high", "critical"];
|
|
5400
|
+
var NEGATION_PREFIX_RE3 = /\b(do not|don't|dont|never|avoid|must not|should not|without changing|without touching|no changes to|keep .* untouched|leave .* untouched|keep .* unchanged)\b/i;
|
|
5401
|
+
function maxRisk(a, b) {
|
|
5402
|
+
return RISK_ORDER[Math.max(RISK_ORDER.indexOf(a), RISK_ORDER.indexOf(b))];
|
|
5403
|
+
}
|
|
5404
|
+
function hasNegationNear3(text, index) {
|
|
5405
|
+
const start = Math.max(0, index - 64);
|
|
5406
|
+
const window = text.slice(start, index + 8);
|
|
5407
|
+
return NEGATION_PREFIX_RE3.test(window);
|
|
5408
|
+
}
|
|
5409
|
+
function hasPositiveKeywordMention3(taskLower, keyword) {
|
|
5410
|
+
let idx = taskLower.indexOf(keyword.toLowerCase());
|
|
5411
|
+
while (idx !== -1) {
|
|
5412
|
+
if (!hasNegationNear3(taskLower, idx)) return true;
|
|
5413
|
+
idx = taskLower.indexOf(keyword.toLowerCase(), idx + keyword.length);
|
|
5414
|
+
}
|
|
5415
|
+
return false;
|
|
5416
|
+
}
|
|
5000
5417
|
function isFastPathEligible(risk, category, guardMode, hasExplicitPaths) {
|
|
5001
5418
|
if (guardMode === "strict") return false;
|
|
5002
5419
|
if (guardMode === "off") return true;
|
|
@@ -5012,8 +5429,16 @@ function generatePlan(cwd, task, runs, config, currentChangedFiles) {
|
|
|
5012
5429
|
const compiler = compileTask(task);
|
|
5013
5430
|
const guardMode = (_a2 = config.autoGuardMode) != null ? _a2 : "smart";
|
|
5014
5431
|
const adapters = detectAdapters(cwd);
|
|
5015
|
-
const riskFiles = compiler.explicitPaths.length > 0 ? compiler.explicitPaths :
|
|
5016
|
-
|
|
5432
|
+
const riskFiles = compiler.explicitPaths.length > 0 ? compiler.explicitPaths : [];
|
|
5433
|
+
let rawRisk = classifyFileRisk(riskFiles);
|
|
5434
|
+
if (ALWAYS_CONTRACT_CATEGORIES.has(compiler.taskCategory)) {
|
|
5435
|
+
rawRisk = maxRisk(rawRisk, "high");
|
|
5436
|
+
}
|
|
5437
|
+
const lowerTask = task.toLowerCase();
|
|
5438
|
+
const criticalSystemMentions = ["auth", "billing", "payment", "webhook", "database", "migration", "middleware"].filter((k) => hasPositiveKeywordMention3(lowerTask, k)).length;
|
|
5439
|
+
if (criticalSystemMentions >= 2) {
|
|
5440
|
+
rawRisk = maxRisk(rawRisk, "critical");
|
|
5441
|
+
}
|
|
5017
5442
|
const catScope = buildCategoryScope(
|
|
5018
5443
|
compiler.taskCategory,
|
|
5019
5444
|
true,
|
|
@@ -5137,6 +5562,22 @@ var FAST_LOCAL_KEYWORDS = [
|
|
|
5137
5562
|
"responsive",
|
|
5138
5563
|
"ui polish"
|
|
5139
5564
|
];
|
|
5565
|
+
var NEGATION_PREFIX_RE4 = /\b(do not|don't|dont|never|avoid|must not|should not|without changing|without touching|no changes to|keep .* untouched|leave .* untouched|keep .* unchanged)\b/i;
|
|
5566
|
+
function hasNegationNear4(text, index) {
|
|
5567
|
+
const start = Math.max(0, index - 64);
|
|
5568
|
+
const window = text.slice(start, index + 8);
|
|
5569
|
+
return NEGATION_PREFIX_RE4.test(window);
|
|
5570
|
+
}
|
|
5571
|
+
function hasPositiveKeywordMention4(task, keyword) {
|
|
5572
|
+
const lowerTask = task.toLowerCase();
|
|
5573
|
+
const lowerKeyword = keyword.toLowerCase();
|
|
5574
|
+
let idx = lowerTask.indexOf(lowerKeyword);
|
|
5575
|
+
while (idx !== -1) {
|
|
5576
|
+
if (!hasNegationNear4(lowerTask, idx)) return true;
|
|
5577
|
+
idx = lowerTask.indexOf(lowerKeyword, idx + lowerKeyword.length);
|
|
5578
|
+
}
|
|
5579
|
+
return false;
|
|
5580
|
+
}
|
|
5140
5581
|
function normalize(s) {
|
|
5141
5582
|
return (s != null ? s : "").toLowerCase();
|
|
5142
5583
|
}
|
|
@@ -5195,9 +5636,9 @@ function recommendProviderRouting(ctx) {
|
|
|
5195
5636
|
const changedFiles = (_d = ctx.changedFiles) != null ? _d : [];
|
|
5196
5637
|
const learnedContext = (_e = ctx.learnedContext) != null ? _e : [];
|
|
5197
5638
|
const hasProofGapSignals = proofRequired.some((p) => /proof gap|missing|vercel log|manual verification/i.test(p));
|
|
5198
|
-
const highRiskByKeyword = HIGH_RISK_KEYWORDS.some((k) => task
|
|
5639
|
+
const highRiskByKeyword = HIGH_RISK_KEYWORDS.some((k) => hasPositiveKeywordMention4(task, k) || hasPositiveKeywordMention4(category, k));
|
|
5199
5640
|
const fastKeyword = FAST_LOCAL_KEYWORDS.some((k) => task.includes(k));
|
|
5200
|
-
const multiCritical = ["auth", "billing", "database", "webhook", "payment", "migration"].filter((k) => task
|
|
5641
|
+
const multiCritical = ["auth", "billing", "database", "webhook", "payment", "migration"].filter((k) => hasPositiveKeywordMention4(task, k)).length >= 2;
|
|
5201
5642
|
const broadTask = !ctx.explicitScope && task.split(/\s+/).length > 16;
|
|
5202
5643
|
const hasSensitiveFiles = sensitiveAreas.length > 0 || changedFiles.some((f) => HIGH_RISK_KEYWORDS.some((k) => normalize(f).includes(k)));
|
|
5203
5644
|
const noLearning = learnedContext.length === 0 && ((_f = ctx.similarRunsCount) != null ? _f : 0) === 0;
|
|
@@ -5462,6 +5903,19 @@ async function getSensitivePathStates(cwd) {
|
|
|
5462
5903
|
return /* @__PURE__ */ new Map();
|
|
5463
5904
|
}
|
|
5464
5905
|
}
|
|
5906
|
+
function extractBoundaryLabels(forbiddenScope) {
|
|
5907
|
+
const labels = /* @__PURE__ */ new Set();
|
|
5908
|
+
for (const line of forbiddenScope) {
|
|
5909
|
+
const lower = line.toLowerCase();
|
|
5910
|
+
if (lower.includes("billing") || lower.includes("subscription") || lower.includes("payment")) labels.add("billing");
|
|
5911
|
+
if (lower.includes("auth") || lower.includes("session") || lower.includes("jwt")) labels.add("auth");
|
|
5912
|
+
if (lower.includes("middleware") || lower.includes("proxy")) labels.add("middleware");
|
|
5913
|
+
if (lower.includes(".env") || lower.includes("secret")) labels.add("env");
|
|
5914
|
+
if (lower.includes("cli")) labels.add("cli");
|
|
5915
|
+
if (lower.includes("mcp")) labels.add("mcp");
|
|
5916
|
+
}
|
|
5917
|
+
return [...labels];
|
|
5918
|
+
}
|
|
5465
5919
|
function nowId() {
|
|
5466
5920
|
const d = /* @__PURE__ */ new Date();
|
|
5467
5921
|
const pad = (n) => String(n).padStart(2, "0");
|
|
@@ -5523,8 +5977,21 @@ function buildApprovalLevel(risk, task, category) {
|
|
|
5523
5977
|
const text = `${task}
|
|
5524
5978
|
${category}`.toLowerCase();
|
|
5525
5979
|
const highSystems = ["auth", "billing", "payment", "dodo", "stripe", "webhook", "database", "migration", "rls", "middleware", "env", "secret"];
|
|
5980
|
+
const hasNegationNear5 = (source, index) => {
|
|
5981
|
+
const start = Math.max(0, index - 64);
|
|
5982
|
+
const window = source.slice(start, index + 8);
|
|
5983
|
+
return /\b(do not|don't|dont|never|avoid|must not|should not|without changing|without touching|no changes to|keep .* untouched|leave .* untouched|keep .* unchanged)\b/i.test(window);
|
|
5984
|
+
};
|
|
5985
|
+
const hasPositiveKeyword = (source, keyword) => {
|
|
5986
|
+
let idx = source.indexOf(keyword);
|
|
5987
|
+
while (idx !== -1) {
|
|
5988
|
+
if (!hasNegationNear5(source, idx)) return true;
|
|
5989
|
+
idx = source.indexOf(keyword, idx + keyword.length);
|
|
5990
|
+
}
|
|
5991
|
+
return false;
|
|
5992
|
+
};
|
|
5526
5993
|
if (risk === "high" || risk === "critical") return "required";
|
|
5527
|
-
if (highSystems.some((k) => text
|
|
5994
|
+
if (highSystems.some((k) => hasPositiveKeyword(text, k))) return "required";
|
|
5528
5995
|
if (risk === "medium") return "recommended";
|
|
5529
5996
|
return "no";
|
|
5530
5997
|
}
|
|
@@ -5565,6 +6032,7 @@ function writePreviewArtifacts(cwd, preview) {
|
|
|
5565
6032
|
"",
|
|
5566
6033
|
"Forbidden:",
|
|
5567
6034
|
...preview.forbiddenScope.length > 0 ? preview.forbiddenScope.slice(0, 8).map((f) => `- ${f}`) : ["- none"],
|
|
6035
|
+
...preview.boundariesDetected.length > 0 ? ["", `Boundaries detected: ${preview.boundariesDetected.join(", ")} will be forbidden, not treated as active scope.`] : [],
|
|
5568
6036
|
"",
|
|
5569
6037
|
"Learned context:",
|
|
5570
6038
|
...preview.learnedContext.length > 0 ? preview.learnedContext.map((x) => `- ${x}`) : ["- learning not available yet"],
|
|
@@ -5604,6 +6072,9 @@ async function runAgentPreview(task) {
|
|
|
5604
6072
|
console.log("");
|
|
5605
6073
|
console.log(GO_ACCENT.bold("Forbidden"));
|
|
5606
6074
|
for (const item of preview.forbiddenScope.slice(0, 6)) console.log(DIM(" - ") + chalk.white(item));
|
|
6075
|
+
if (preview.boundariesDetected.length > 0) {
|
|
6076
|
+
console.log(DIM(" Boundaries detected: ") + chalk.white(`${preview.boundariesDetected.join(", ")} will be forbidden, not treated as active scope.`));
|
|
6077
|
+
}
|
|
5607
6078
|
console.log("");
|
|
5608
6079
|
console.log(GO_ACCENT.bold("Patch strategy"));
|
|
5609
6080
|
for (let i = 0; i < preview.patchStrategy.length; i += 1) {
|
|
@@ -5668,6 +6139,7 @@ async function buildAgentPreview(task) {
|
|
|
5668
6139
|
filesToInspect,
|
|
5669
6140
|
allowedScope: contract.contract.relevantScope,
|
|
5670
6141
|
forbiddenScope: contract.contract.forbiddenScope,
|
|
6142
|
+
boundariesDetected: extractBoundaryLabels(contract.contract.forbiddenScope),
|
|
5671
6143
|
sensitiveAreas: [...contract.contract.sensitiveScope, ...plan.sensitiveAreas].slice(0, 10),
|
|
5672
6144
|
stopRules: contract.contract.stopRules,
|
|
5673
6145
|
successCriteria: contract.contract.successCriteria,
|
|
@@ -6984,6 +7456,24 @@ async function buildRuntrimCreateContractMcp(cwd, args) {
|
|
|
6984
7456
|
isError: true
|
|
6985
7457
|
};
|
|
6986
7458
|
}
|
|
7459
|
+
const repoCheck = await assertFreeRepoAllowed(cwd);
|
|
7460
|
+
if (!repoCheck.allowed) {
|
|
7461
|
+
const guidance = repoCheck.status === "blocked_repair" ? "RunTrim local state needs repair. Free includes 1 tracked repo. The local repo registry changed unexpectedly. Repair the registry or upgrade to Builder for unlimited repos." : "Free includes 1 tracked repo. This repo is not currently tracked. Continue in the tracked repo, unlink the tracked repo with runtrim repo unlink --force, or upgrade to Builder for unlimited repos.";
|
|
7462
|
+
const blockedPayload = {
|
|
7463
|
+
contract_created: false,
|
|
7464
|
+
task: taskRaw,
|
|
7465
|
+
error: repoCheck.status === "blocked_repair" ? "repo_registry_repair_required" : "repo_limit_blocked",
|
|
7466
|
+
guidance,
|
|
7467
|
+
next_action: guidance,
|
|
7468
|
+
finish_command: "runtrim finish",
|
|
7469
|
+
approval_command_example: 'runtrim approve "Allow <path> for this run only"'
|
|
7470
|
+
};
|
|
7471
|
+
return {
|
|
7472
|
+
content: [{ type: "text", text: JSON.stringify(blockedPayload, null, 2) }],
|
|
7473
|
+
structuredContent: blockedPayload,
|
|
7474
|
+
isError: true
|
|
7475
|
+
};
|
|
7476
|
+
}
|
|
6987
7477
|
const latest = loadLatestRun(cwd);
|
|
6988
7478
|
if ((latest == null ? void 0 : latest.status) === "guarded") {
|
|
6989
7479
|
const blockedPayload = {
|
|
@@ -7645,6 +8135,21 @@ function isInteractiveTerminal() {
|
|
|
7645
8135
|
async function ensureRepoAllowedForFree(cwd) {
|
|
7646
8136
|
var _a2, _b;
|
|
7647
8137
|
const check = await assertFreeRepoAllowed(cwd);
|
|
8138
|
+
if (check.status === "blocked_repair") {
|
|
8139
|
+
console.log(chalk.yellow(" RunTrim local state needs repair."));
|
|
8140
|
+
console.log(chalk.yellow(" Free includes 1 tracked repo."));
|
|
8141
|
+
console.log(chalk.yellow(" Your local repo registry changed unexpectedly."));
|
|
8142
|
+
console.log("");
|
|
8143
|
+
console.log(DIM(" Next:"));
|
|
8144
|
+
console.log(chalk.white(" - runtrim repo status"));
|
|
8145
|
+
console.log(chalk.white(" - runtrim repo repair"));
|
|
8146
|
+
console.log(chalk.white(" - runtrim repo repair --use-current"));
|
|
8147
|
+
console.log(chalk.white(" - runtrim repo unlink --force"));
|
|
8148
|
+
console.log(chalk.white(" - upgrade to Builder for unlimited repos"));
|
|
8149
|
+
console.log(chalk.white(" - sign in to restore cloud entitlements"));
|
|
8150
|
+
console.log("");
|
|
8151
|
+
return false;
|
|
8152
|
+
}
|
|
7648
8153
|
if (check.allowed) {
|
|
7649
8154
|
await registerCurrentRepo(cwd);
|
|
7650
8155
|
return true;
|
|
@@ -7658,9 +8163,12 @@ async function ensureRepoAllowedForFree(cwd) {
|
|
|
7658
8163
|
console.log(chalk.white(` ${check.currentRepo.path}`));
|
|
7659
8164
|
console.log("");
|
|
7660
8165
|
console.log(DIM(" Next:"));
|
|
7661
|
-
console.log(
|
|
7662
|
-
|
|
7663
|
-
|
|
8166
|
+
console.log(
|
|
8167
|
+
chalk.white(
|
|
8168
|
+
" Free includes 1 tracked repo. This repo is not currently tracked. Continue in the tracked repo, unlink the tracked repo with runtrim repo unlink --force, or upgrade to Builder for unlimited repos."
|
|
8169
|
+
)
|
|
8170
|
+
);
|
|
8171
|
+
console.log(chalk.white(" Agent instructions were not installed because this repo is not tracked."));
|
|
7664
8172
|
console.log("");
|
|
7665
8173
|
console.log(
|
|
7666
8174
|
DIM(
|
|
@@ -8027,7 +8535,8 @@ var PROTOCOL_POINTER_BLOCK = `
|
|
|
8027
8535
|
${PROTOCOL_BLOCK_START}
|
|
8028
8536
|
This repo uses RunTrim as the guarded AI coding protocol.
|
|
8029
8537
|
Before editing code, read RUNTRIM.md.
|
|
8030
|
-
Start every task with: runtrim
|
|
8538
|
+
Start every task with: runtrim start
|
|
8539
|
+
Then run: runtrim agent "Your task" --copy
|
|
8031
8540
|
Stay inside .runtrim/contracts/latest.md.
|
|
8032
8541
|
After edits, ask the user to run: runtrim finish
|
|
8033
8542
|
${PROTOCOL_BLOCK_END}
|
|
@@ -8723,6 +9232,8 @@ program.command("execute <task>").description("Create a controlled execution pac
|
|
|
8723
9232
|
var agentCommand = program.command("agent").description("Start a guarded AI coding run with contract, scope, memory, and handoff");
|
|
8724
9233
|
agentCommand.argument("[task]").option("--copy", "Copy the handoff to clipboard").option("--bridge", "Ensure local bridge is running for this agent run").option("--preview", "Generate an execution preview instead of running any agent").option("--apply", "Generate Agent Apply handoff artifacts").option("--execute", "Create a controlled execution packet and handoff").option("--run", "Alias for --execute").option("--dry-run", "Create execution packet in pending mode without ready status").option("--confirm", "Confirm high-risk apply handoff creation").action(async (task, options) => {
|
|
8725
9234
|
if (task == null ? void 0 : task.trim()) {
|
|
9235
|
+
const allowed = await ensureRepoAllowedForFree(process.cwd());
|
|
9236
|
+
if (!allowed) return;
|
|
8726
9237
|
const normalizedTask = (task != null ? task : "").trim();
|
|
8727
9238
|
if (options == null ? void 0 : options.bridge) {
|
|
8728
9239
|
const bridge = await ensureBridgeRunningForAgent(process.cwd());
|
|
@@ -9191,12 +9702,54 @@ repoCommand.command("status").description("Show local tracked repo status").acti
|
|
|
9191
9702
|
console.log(DIM(" Current repo ") + chalk.white(identity.path));
|
|
9192
9703
|
console.log(DIM(" Tracked repo ") + chalk.white((_b = tracked == null ? void 0 : tracked.path) != null ? _b : "(none)"));
|
|
9193
9704
|
console.log(DIM(" Allowed ") + chalk.white(check.allowed ? "yes" : "no"));
|
|
9705
|
+
console.log(DIM(" State ") + chalk.white(check.status));
|
|
9194
9706
|
console.log("");
|
|
9707
|
+
if (check.status === "blocked_repair") {
|
|
9708
|
+
console.log(chalk.yellow(" RunTrim local state needs repair."));
|
|
9709
|
+
console.log(chalk.yellow(" Free includes 1 tracked repo."));
|
|
9710
|
+
console.log(chalk.yellow(" The local repo registry changed unexpectedly."));
|
|
9711
|
+
console.log(DIM(" Run: runtrim repo repair"));
|
|
9712
|
+
console.log("");
|
|
9713
|
+
}
|
|
9195
9714
|
if (tracked) {
|
|
9196
9715
|
console.log(DIM(" A tracked repo is one codebase with its own .runtrim workspace."));
|
|
9197
9716
|
console.log("");
|
|
9198
9717
|
}
|
|
9199
9718
|
});
|
|
9719
|
+
repoCommand.command("repair").description("Repair local free-plan repo registry integrity").option("--use-current", "Repair and set the current repo as the single tracked Free repo").action(async (options) => {
|
|
9720
|
+
const cwd = process.cwd();
|
|
9721
|
+
const before = await assertFreeRepoAllowed(cwd);
|
|
9722
|
+
console.log("");
|
|
9723
|
+
console.log(BOLD("RunTrim") + DIM(" repo repair"));
|
|
9724
|
+
console.log("");
|
|
9725
|
+
if (!before.repairRequired) {
|
|
9726
|
+
console.log(DIM(" Local state is healthy. No repair required."));
|
|
9727
|
+
console.log("");
|
|
9728
|
+
return;
|
|
9729
|
+
}
|
|
9730
|
+
if (!options.useCurrent) {
|
|
9731
|
+
console.log(chalk.yellow(" RunTrim local state needs repair."));
|
|
9732
|
+
console.log(chalk.yellow(" Free includes 1 tracked repo."));
|
|
9733
|
+
console.log(chalk.yellow(" Your local repo registry changed unexpectedly."));
|
|
9734
|
+
console.log("");
|
|
9735
|
+
console.log(DIM(" Safe next actions:"));
|
|
9736
|
+
console.log(chalk.white(" - runtrim repo repair --use-current"));
|
|
9737
|
+
console.log(chalk.white(" - runtrim repo unlink --force"));
|
|
9738
|
+
console.log(chalk.white(" - upgrade to Builder for unlimited repos"));
|
|
9739
|
+
console.log(chalk.white(" - sign in to restore cloud entitlements"));
|
|
9740
|
+
console.log("");
|
|
9741
|
+
return;
|
|
9742
|
+
}
|
|
9743
|
+
const result = await repairGlobalRegistry(cwd, { useCurrentRepo: true });
|
|
9744
|
+
if (result.repaired) {
|
|
9745
|
+
console.log(ACCENT.bold(" Local registry repaired."));
|
|
9746
|
+
console.log(DIM(" Current repo is now the tracked Free repo."));
|
|
9747
|
+
console.log("");
|
|
9748
|
+
return;
|
|
9749
|
+
}
|
|
9750
|
+
console.log(DIM(" No repair changes applied."));
|
|
9751
|
+
console.log("");
|
|
9752
|
+
});
|
|
9200
9753
|
repoCommand.command("unlink").description("Unlink tracked repo from local free-plan registry").option("--force", "Force unlink tracked repo even when running from another path").action(async (options) => {
|
|
9201
9754
|
const cwd = process.cwd();
|
|
9202
9755
|
const result = await unlinkCurrentRepo(cwd, Boolean(options.force));
|
|
@@ -10363,7 +10916,7 @@ program.command("continue").description("Create a safe continuation prompt from
|
|
|
10363
10916
|
const audit = (_a2 = loadProjectAudit(cwd)) != null ? _a2 : performBaselineProjectAudit(cwd, null);
|
|
10364
10917
|
const reason = normalizeContinuationReason(options.reason);
|
|
10365
10918
|
const agent = normalizeContinuationAgent(options.agent, config.defaultAgent);
|
|
10366
|
-
const
|
|
10919
|
+
const nowIso2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
10367
10920
|
const continuationPath = resolveContinuationPath(cwd);
|
|
10368
10921
|
const latestPromptPath = resolvePromptPath(config, cwd);
|
|
10369
10922
|
const latestPrompt = fs13.existsSync(latestPromptPath) ? fs13.readFileSync(latestPromptPath, "utf-8").trim() : "";
|
|
@@ -10553,14 +11106,14 @@ program.command("continue").description("Create a safe continuation prompt from
|
|
|
10553
11106
|
fs13.writeFileSync(continuationPath, continuationPrompt, "utf-8");
|
|
10554
11107
|
const copied = await copyToClipboardSafe(continuationPrompt);
|
|
10555
11108
|
if (memory) {
|
|
10556
|
-
const memoryWithContinuation = updateMemoryWithContinuation(memory, reason, continuationPath,
|
|
11109
|
+
const memoryWithContinuation = updateMemoryWithContinuation(memory, reason, continuationPath, nowIso2);
|
|
10557
11110
|
fs13.writeFileSync(path13.join(getConfigDir(cwd), "memory.md"), memoryWithContinuation, "utf-8");
|
|
10558
11111
|
}
|
|
10559
11112
|
if (hasConfig) {
|
|
10560
11113
|
const nextConfig = __spreadProps(__spreadValues({}, config), {
|
|
10561
11114
|
lastContinuationReason: reason,
|
|
10562
11115
|
continuationPromptPath: continuationPath.replace(/\\/g, "/"),
|
|
10563
|
-
continuationCreatedAt:
|
|
11116
|
+
continuationCreatedAt: nowIso2
|
|
10564
11117
|
});
|
|
10565
11118
|
saveConfig(nextConfig, cwd);
|
|
10566
11119
|
}
|
|
@@ -11352,6 +11905,8 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
|
|
|
11352
11905
|
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) => {
|
|
11353
11906
|
var _a2, _b;
|
|
11354
11907
|
const cwd = process.cwd();
|
|
11908
|
+
const allowed = await ensureRepoAllowedForFree(cwd);
|
|
11909
|
+
if (!allowed) return;
|
|
11355
11910
|
console.log("");
|
|
11356
11911
|
console.log(BOLD("RunTrim") + DIM(" cloud sync"));
|
|
11357
11912
|
console.log("");
|