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.cjs
CHANGED
|
@@ -169,6 +169,22 @@ var ENV_FILE_RE = /(?:^|[\s"'`,(])(\.[.]?env(?:\.[a-zA-Z\d]+)?)\b/g;
|
|
|
169
169
|
var ONLY_EDIT_RE = /\bonly\s+(?:edit|touch|modify|change|update|fix)\b/i;
|
|
170
170
|
var MUST_INCLUDE_RE = /\ballowed\s+scope\s+(?:must\s+)?include\b|\bmust\s+(?:include|contain)\b/i;
|
|
171
171
|
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;
|
|
172
|
+
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;
|
|
173
|
+
function hasNegationNear(text, index) {
|
|
174
|
+
const start = Math.max(0, index - 64);
|
|
175
|
+
const window = text.slice(start, index + 8);
|
|
176
|
+
return NEGATION_PREFIX_RE.test(window);
|
|
177
|
+
}
|
|
178
|
+
function hasPositiveKeywordMention(task, keyword) {
|
|
179
|
+
const lowerTask = task.toLowerCase();
|
|
180
|
+
const lowerKeyword = keyword.toLowerCase();
|
|
181
|
+
let idx = lowerTask.indexOf(lowerKeyword);
|
|
182
|
+
while (idx !== -1) {
|
|
183
|
+
if (!hasNegationNear(lowerTask, idx)) return true;
|
|
184
|
+
idx = lowerTask.indexOf(lowerKeyword, idx + lowerKeyword.length);
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
172
188
|
function extractScopePhrase(task, re) {
|
|
173
189
|
var _a2, _b;
|
|
174
190
|
const m = task.match(re);
|
|
@@ -226,18 +242,34 @@ function buildExplicitAllowedScope(task, explicitPaths) {
|
|
|
226
242
|
}
|
|
227
243
|
function buildExplicitForbiddenScope(task) {
|
|
228
244
|
const out = [];
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
245
|
+
const addBoundaryList = (raw) => {
|
|
246
|
+
if (!raw) return;
|
|
247
|
+
const normalized = raw.replace(/\b(logic|internals?|behavior|files?|systems?)\b/gi, "").replace(/\s+/g, " ").trim();
|
|
248
|
+
const parts = normalized.split(/\s*(?:,|;|\band\b|\bor\b)\s*/i).map((p) => p.trim().replace(/[.]+$/, "")).filter(Boolean).slice(0, 12);
|
|
249
|
+
for (const p of parts) {
|
|
250
|
+
if (p.length < 2) continue;
|
|
251
|
+
out.push(`Do not touch ${p}`);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
const explicitPhrases = [
|
|
255
|
+
/\bforbidden\s+scope\s+must\s+include\s+([^\n.]+)/i,
|
|
256
|
+
/\bdo\s+not\s+touch\s+([^\n.]+)/i,
|
|
257
|
+
/\bdo\s+not\s+edit\s+([^\n.]+)/i,
|
|
258
|
+
/\bdo\s+not\s+change\s+([^\n.]+)/i,
|
|
259
|
+
/\bmust\s+not\s+touch\s+([^\n.]+)/i,
|
|
260
|
+
/\bshould\s+not\s+touch\s+([^\n.]+)/i,
|
|
261
|
+
/\bwithout\s+changing\s+([^\n.]+)/i,
|
|
262
|
+
/\bwithout\s+touching\s+([^\n.]+)/i,
|
|
263
|
+
/\bno\s+changes\s+to\s+([^\n.]+)/i,
|
|
264
|
+
/\bkeep\s+([^\n.]+?)\s+(?:untouched|unchanged)\b/i,
|
|
265
|
+
/\bleave\s+([^\n.]+?)\s+untouched\b/i,
|
|
266
|
+
/\bavoid\s+changing\s+([^\n.]+)/i,
|
|
267
|
+
/\bexclude\s+([^\n.]+)/i,
|
|
268
|
+
/\bforbidden\s+([^\n.]+)/i
|
|
269
|
+
];
|
|
270
|
+
for (const re of explicitPhrases) {
|
|
271
|
+
addBoundaryList(extractScopePhrase(task, re));
|
|
272
|
+
}
|
|
241
273
|
return [...new Set(out)];
|
|
242
274
|
}
|
|
243
275
|
function extractExplicitPaths(task) {
|
|
@@ -469,11 +501,10 @@ var CATEGORY_KEYWORDS = [
|
|
|
469
501
|
];
|
|
470
502
|
function classifyTaskCategory(task, explicitPaths) {
|
|
471
503
|
const lower = task.toLowerCase();
|
|
472
|
-
if (CLI_SCOPE_RE.test(task)) return "cli";
|
|
473
504
|
const pathHints = explicitPaths.join(" ").toLowerCase();
|
|
474
505
|
for (const [category, keywords] of CATEGORY_KEYWORDS) {
|
|
475
506
|
const combined = lower + " " + pathHints;
|
|
476
|
-
if (keywords.some((kw) => combined
|
|
507
|
+
if (keywords.some((kw) => hasPositiveKeywordMention(combined, kw))) {
|
|
477
508
|
return category;
|
|
478
509
|
}
|
|
479
510
|
}
|
|
@@ -656,6 +687,28 @@ function buildCategoryScope(category, hasSrc, hasApp, hasPages) {
|
|
|
656
687
|
"Check no regression in adjacent routes"
|
|
657
688
|
]
|
|
658
689
|
};
|
|
690
|
+
case "docs":
|
|
691
|
+
return {
|
|
692
|
+
allowedHints: [
|
|
693
|
+
"README.md - project documentation",
|
|
694
|
+
"docs/ - documentation files",
|
|
695
|
+
"CHANGELOG.md or CONTRIBUTING.md if task-specific"
|
|
696
|
+
],
|
|
697
|
+
forbiddenAdditions: [
|
|
698
|
+
"Do not touch auth internals, session logic, or JWT handling",
|
|
699
|
+
"Do not touch billing, subscription, payment, or webhook logic",
|
|
700
|
+
"Do not touch database schema or migrations",
|
|
701
|
+
"Do not touch .env files or secrets"
|
|
702
|
+
],
|
|
703
|
+
stopRules: [
|
|
704
|
+
"Stop if the requested change requires code-path behavior changes outside docs",
|
|
705
|
+
"Stop if sensitive files or secrets are referenced"
|
|
706
|
+
],
|
|
707
|
+
verificationSteps: [
|
|
708
|
+
"Confirm documentation text matches the requested task",
|
|
709
|
+
"Check markdown formatting renders correctly"
|
|
710
|
+
]
|
|
711
|
+
};
|
|
659
712
|
default:
|
|
660
713
|
return {
|
|
661
714
|
allowedHints: [],
|
|
@@ -716,6 +769,28 @@ var LOOP_PATTERNS = [
|
|
|
716
769
|
/\b(keep (trying|going|working)|iterate until|loop until|retry)\b/i,
|
|
717
770
|
/\b(if it doesn.t work.{0,20}try again)\b/i
|
|
718
771
|
];
|
|
772
|
+
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;
|
|
773
|
+
function hasNegationNear2(text, index) {
|
|
774
|
+
const start = Math.max(0, index - 64);
|
|
775
|
+
const window = text.slice(start, index + 8);
|
|
776
|
+
return NEGATION_PREFIX_RE2.test(window);
|
|
777
|
+
}
|
|
778
|
+
function hasPositiveKeywordMention2(taskLower, keyword) {
|
|
779
|
+
let idx = taskLower.indexOf(keyword.toLowerCase());
|
|
780
|
+
while (idx !== -1) {
|
|
781
|
+
if (!hasNegationNear2(taskLower, idx)) return true;
|
|
782
|
+
idx = taskLower.indexOf(keyword.toLowerCase(), idx + keyword.length);
|
|
783
|
+
}
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
function hasNegatedKeywordMention(taskLower, keyword) {
|
|
787
|
+
let idx = taskLower.indexOf(keyword.toLowerCase());
|
|
788
|
+
while (idx !== -1) {
|
|
789
|
+
if (hasNegationNear2(taskLower, idx)) return true;
|
|
790
|
+
idx = taskLower.indexOf(keyword.toLowerCase(), idx + keyword.length);
|
|
791
|
+
}
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
719
794
|
function scoreTask(task, flags) {
|
|
720
795
|
let score = 100;
|
|
721
796
|
for (const flag of flags) {
|
|
@@ -776,7 +851,7 @@ function detectProjectContext(cwd = process.cwd()) {
|
|
|
776
851
|
function detectMegaRun(taskLower, task) {
|
|
777
852
|
const found = [];
|
|
778
853
|
for (const [system, keywords] of Object.entries(MEGA_RUN_SYSTEMS)) {
|
|
779
|
-
if (keywords.some((kw) => taskLower
|
|
854
|
+
if (keywords.some((kw) => hasPositiveKeywordMention2(taskLower, kw))) {
|
|
780
855
|
found.push(system);
|
|
781
856
|
}
|
|
782
857
|
}
|
|
@@ -787,17 +862,24 @@ function detectMegaRun(taskLower, task) {
|
|
|
787
862
|
function detectAreasTouched(taskLower) {
|
|
788
863
|
const forbidden = [];
|
|
789
864
|
const sensitive = [];
|
|
865
|
+
const boundaries = [];
|
|
790
866
|
for (const [area, keywords] of Object.entries(ALWAYS_FORBIDDEN_KEYWORDS)) {
|
|
791
|
-
if (keywords.some((kw) => taskLower
|
|
867
|
+
if (keywords.some((kw) => hasPositiveKeywordMention2(taskLower, kw))) {
|
|
792
868
|
forbidden.push(area);
|
|
793
869
|
}
|
|
870
|
+
if (keywords.some((kw) => hasNegatedKeywordMention(taskLower, kw))) {
|
|
871
|
+
boundaries.push(area);
|
|
872
|
+
}
|
|
794
873
|
}
|
|
795
874
|
for (const [area, keywords] of Object.entries(SENSITIVE_BILLING_KEYWORDS)) {
|
|
796
|
-
if (keywords.some((kw) => taskLower
|
|
875
|
+
if (keywords.some((kw) => hasPositiveKeywordMention2(taskLower, kw))) {
|
|
797
876
|
sensitive.push(area);
|
|
798
877
|
}
|
|
878
|
+
if (keywords.some((kw) => hasNegatedKeywordMention(taskLower, kw))) {
|
|
879
|
+
boundaries.push(area);
|
|
880
|
+
}
|
|
799
881
|
}
|
|
800
|
-
return { forbidden, sensitive };
|
|
882
|
+
return { forbidden, sensitive, boundaries: [...new Set(boundaries)] };
|
|
801
883
|
}
|
|
802
884
|
function auditTask(task, config, cwd = process.cwd()) {
|
|
803
885
|
const flags = [];
|
|
@@ -887,7 +969,7 @@ function auditTask(task, config, cwd = process.cwd()) {
|
|
|
887
969
|
detail: "References to full context or entire conversation force expensive context loading."
|
|
888
970
|
});
|
|
889
971
|
}
|
|
890
|
-
const { forbidden: forbiddenAreasTouched, sensitive: sensitiveAreasRelevant } = detectAreasTouched(taskLower);
|
|
972
|
+
const { forbidden: forbiddenAreasTouched, sensitive: sensitiveAreasRelevant, boundaries } = detectAreasTouched(taskLower);
|
|
891
973
|
if (forbiddenAreasTouched.length > 0) {
|
|
892
974
|
flags.push({
|
|
893
975
|
code: "touches_forbidden_area",
|
|
@@ -904,6 +986,14 @@ function auditTask(task, config, cwd = process.cwd()) {
|
|
|
904
986
|
detail: `Task touches ${sensitiveAreasRelevant.join(", ")}. These are moved to SENSITIVE SCOPE: inspect allowed, editing requires explicit approval.`
|
|
905
987
|
});
|
|
906
988
|
}
|
|
989
|
+
if (boundaries.length > 0) {
|
|
990
|
+
flags.push({
|
|
991
|
+
code: "forbidden_boundaries_detected",
|
|
992
|
+
label: `Boundaries detected: ${boundaries.join(", ")}`,
|
|
993
|
+
severity: "info",
|
|
994
|
+
detail: "Sensitive systems in negated constraints are treated as forbidden boundaries, not active task scope."
|
|
995
|
+
});
|
|
996
|
+
}
|
|
907
997
|
const isSimpleTask = task.length < 80 && flags.filter((f) => f.severity === "critical").length === 0;
|
|
908
998
|
if (isSimpleTask && config.defaultModel === "opus") {
|
|
909
999
|
flags.push({
|
|
@@ -981,6 +1071,10 @@ function scoreToRisk2(score) {
|
|
|
981
1071
|
}
|
|
982
1072
|
function cleanObjective(task) {
|
|
983
1073
|
let t = task.trim();
|
|
1074
|
+
t = t.replace(
|
|
1075
|
+
/(?:^|[\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,
|
|
1076
|
+
" "
|
|
1077
|
+
);
|
|
984
1078
|
t = t.replace(/,?\s*check everything(\s+and\b)?/gi, "");
|
|
985
1079
|
t = t.replace(/,?\s*(look|search|scan)\s+everywhere(\s+and\b)?/gi, "");
|
|
986
1080
|
t = t.replace(/,?\s*review everything(\s+and\b)?/gi, "");
|
|
@@ -2526,7 +2620,7 @@ function buildSyncPayload(input) {
|
|
|
2526
2620
|
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
|
|
2527
2621
|
const { cwd, projectName, config, projectAudit, memoryMarkdown, runs } = input;
|
|
2528
2622
|
const latest = runs[0];
|
|
2529
|
-
const
|
|
2623
|
+
const nowIso2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
2530
2624
|
const localProjectId = buildLocalProjectId(cwd);
|
|
2531
2625
|
const latestPromptText = readTextFileIfExists(import_path6.default.join(cwd, ".runtrim", "latest-prompt.md"));
|
|
2532
2626
|
const continuationPromptText = readTextFileIfExists(
|
|
@@ -2588,7 +2682,7 @@ function buildSyncPayload(input) {
|
|
|
2588
2682
|
name: resolveProjectName2(cwd, projectName, projectAudit == null ? void 0 : projectAudit.projectName),
|
|
2589
2683
|
stack: (_a2 = projectAudit == null ? void 0 : projectAudit.detectedStack) != null ? _a2 : config.stack ? config.stack.split(",").map((s) => s.trim()).filter(Boolean) : ["auto"],
|
|
2590
2684
|
packageManager: (_c = (_b = projectAudit == null ? void 0 : projectAudit.packageManager) != null ? _b : config.packageManager) != null ? _c : null,
|
|
2591
|
-
lastUpdated:
|
|
2685
|
+
lastUpdated: nowIso2
|
|
2592
2686
|
},
|
|
2593
2687
|
memory: {
|
|
2594
2688
|
markdown: memoryMarkdown,
|
|
@@ -2670,7 +2764,8 @@ function writeCanonicalRuntrimMd(cwd = process.cwd(), projectName) {
|
|
|
2670
2764
|
"## How to start an AI coding task",
|
|
2671
2765
|
"",
|
|
2672
2766
|
"```",
|
|
2673
|
-
|
|
2767
|
+
"runtrim start",
|
|
2768
|
+
'runtrim agent "Your task" --copy',
|
|
2674
2769
|
"```",
|
|
2675
2770
|
"",
|
|
2676
2771
|
"RunTrim creates a scoped contract, loads project memory, and generates a guarded prompt.",
|
|
@@ -2691,7 +2786,7 @@ function writeCanonicalRuntrimMd(cwd = process.cwd(), projectName) {
|
|
|
2691
2786
|
"",
|
|
2692
2787
|
"1. Read `.runtrim/contracts/latest.md`.",
|
|
2693
2788
|
" - If `Status: active` \u2014 a live task exists. Follow the contract strictly.",
|
|
2694
|
-
' - If `Status: none` \u2014 no active task. Ask the user to run `runtrim
|
|
2789
|
+
' - If `Status: none` \u2014 no active task. Ask the user to run `runtrim start` then `runtrim agent "Your task" --copy`.',
|
|
2695
2790
|
"2. Do not assume any prior task is still active.",
|
|
2696
2791
|
"3. Stay inside the allowed scope defined in the contract.",
|
|
2697
2792
|
"4. Stop and ask before touching any forbidden area.",
|
|
@@ -2716,7 +2811,8 @@ function writeRestingContract(cwd = process.cwd()) {
|
|
|
2716
2811
|
"Start one with:",
|
|
2717
2812
|
"",
|
|
2718
2813
|
"```",
|
|
2719
|
-
|
|
2814
|
+
"runtrim start",
|
|
2815
|
+
'runtrim agent "Your task" --copy',
|
|
2720
2816
|
"```",
|
|
2721
2817
|
"",
|
|
2722
2818
|
"---",
|
|
@@ -2739,7 +2835,8 @@ function writeRestingMemory(cwd = process.cwd()) {
|
|
|
2739
2835
|
"Start a new session with:",
|
|
2740
2836
|
"",
|
|
2741
2837
|
"```",
|
|
2742
|
-
|
|
2838
|
+
"runtrim start",
|
|
2839
|
+
'runtrim agent "Your task" --copy',
|
|
2743
2840
|
"```",
|
|
2744
2841
|
"",
|
|
2745
2842
|
"---",
|
|
@@ -2860,7 +2957,7 @@ function writeBridgeInstructions(cwd = process.cwd()) {
|
|
|
2860
2957
|
"1. Read `RUNTRIM.md`.",
|
|
2861
2958
|
"2. Read `.runtrim/contracts/latest.md`.",
|
|
2862
2959
|
" - If `Status: active` \u2014 follow the contract strictly.",
|
|
2863
|
-
' - If `Status: none` \u2014 stop. Ask the user to run `runtrim
|
|
2960
|
+
' - If `Status: none` \u2014 stop. Ask the user to run `runtrim start` then `runtrim agent "Your task" --copy`.',
|
|
2864
2961
|
"3. If the contract is active, read `.runtrim/memory/current.md` for session context.",
|
|
2865
2962
|
" If no active session, read `.runtrim/memory/baseline.md` for project baseline.",
|
|
2866
2963
|
"",
|
|
@@ -2934,6 +3031,28 @@ function buildBridgePrompt(contractText, ctx) {
|
|
|
2934
3031
|
// src/lib/run-watch.ts
|
|
2935
3032
|
function normalizeScopeKeywords2(scope) {
|
|
2936
3033
|
var _a2;
|
|
3034
|
+
const genericStopwords = /* @__PURE__ */ new Set([
|
|
3035
|
+
"read",
|
|
3036
|
+
"write",
|
|
3037
|
+
"reference",
|
|
3038
|
+
"touch",
|
|
3039
|
+
"modify",
|
|
3040
|
+
"change",
|
|
3041
|
+
"update",
|
|
3042
|
+
"allow",
|
|
3043
|
+
"scope",
|
|
3044
|
+
"paths",
|
|
3045
|
+
"path",
|
|
3046
|
+
"files",
|
|
3047
|
+
"file",
|
|
3048
|
+
"only",
|
|
3049
|
+
"with",
|
|
3050
|
+
"without",
|
|
3051
|
+
"before",
|
|
3052
|
+
"after",
|
|
3053
|
+
"inside",
|
|
3054
|
+
"outside"
|
|
3055
|
+
]);
|
|
2937
3056
|
const words = /* @__PURE__ */ new Set();
|
|
2938
3057
|
for (const line of scope) {
|
|
2939
3058
|
const lower = line.toLowerCase();
|
|
@@ -2943,7 +3062,7 @@ function normalizeScopeKeywords2(scope) {
|
|
|
2943
3062
|
}
|
|
2944
3063
|
const cleaned = lower.replace(/[^a-z0-9_./\s-]/g, " ").split(/\s+/).filter(Boolean);
|
|
2945
3064
|
for (const token of cleaned) {
|
|
2946
|
-
if (token.length >= 4) words.add(token);
|
|
3065
|
+
if (token.length >= 4 && !genericStopwords.has(token)) words.add(token);
|
|
2947
3066
|
}
|
|
2948
3067
|
}
|
|
2949
3068
|
return [...words];
|
|
@@ -3040,15 +3159,7 @@ var import_fs8 = __toESM(require("fs"), 1);
|
|
|
3040
3159
|
var import_os = __toESM(require("os"), 1);
|
|
3041
3160
|
var import_path8 = __toESM(require("path"), 1);
|
|
3042
3161
|
var import_execa2 = require("execa");
|
|
3043
|
-
var
|
|
3044
|
-
version: 1,
|
|
3045
|
-
plan: "free",
|
|
3046
|
-
trackedRepos: [],
|
|
3047
|
-
telemetry: {
|
|
3048
|
-
enabled: false,
|
|
3049
|
-
anonymousId: ""
|
|
3050
|
-
}
|
|
3051
|
-
};
|
|
3162
|
+
var EMPTY_TELEMETRY = { enabled: false, anonymousId: "" };
|
|
3052
3163
|
function normalizeRepoPath(input) {
|
|
3053
3164
|
const resolved = import_path8.default.resolve(input);
|
|
3054
3165
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
@@ -3056,41 +3167,208 @@ function normalizeRepoPath(input) {
|
|
|
3056
3167
|
function hashValue(value) {
|
|
3057
3168
|
return import_crypto2.default.createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
3058
3169
|
}
|
|
3170
|
+
function nowIso() {
|
|
3171
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3172
|
+
}
|
|
3173
|
+
function randomId(prefix) {
|
|
3174
|
+
return `${prefix}_${import_crypto2.default.randomBytes(12).toString("hex")}`;
|
|
3175
|
+
}
|
|
3059
3176
|
function getGlobalRunTrimDir() {
|
|
3060
3177
|
return import_path8.default.join(import_os.default.homedir(), ".runtrim");
|
|
3061
3178
|
}
|
|
3062
3179
|
function getGlobalRegistryPath() {
|
|
3063
3180
|
return import_path8.default.join(getGlobalRunTrimDir(), "global.json");
|
|
3064
3181
|
}
|
|
3065
|
-
function
|
|
3182
|
+
function getInstallStatePath() {
|
|
3183
|
+
return import_path8.default.join(getGlobalRunTrimDir(), "install-state.json");
|
|
3184
|
+
}
|
|
3185
|
+
function buildSealInput(registry) {
|
|
3186
|
+
const tracked = [...registry.trackedRepos].map((r) => ({
|
|
3187
|
+
id: r.id,
|
|
3188
|
+
name: r.name,
|
|
3189
|
+
path: normalizeRepoPath(r.path),
|
|
3190
|
+
gitRemote: r.gitRemote,
|
|
3191
|
+
createdAt: r.createdAt,
|
|
3192
|
+
lastSeenAt: r.lastSeenAt
|
|
3193
|
+
})).sort((a, b) => `${a.id}:${a.path}`.localeCompare(`${b.id}:${b.path}`));
|
|
3194
|
+
const payload = {
|
|
3195
|
+
version: registry.version,
|
|
3196
|
+
stateVersion: registry.stateVersion,
|
|
3197
|
+
plan: registry.plan,
|
|
3198
|
+
machineInstallId: registry.machineInstallId,
|
|
3199
|
+
createdAt: registry.createdAt,
|
|
3200
|
+
updatedAt: registry.updatedAt,
|
|
3201
|
+
trackedRepos: tracked,
|
|
3202
|
+
lastKnownRepo: registry.lastKnownRepo ? __spreadProps(__spreadValues({}, registry.lastKnownRepo), {
|
|
3203
|
+
path: normalizeRepoPath(registry.lastKnownRepo.path)
|
|
3204
|
+
}) : null
|
|
3205
|
+
};
|
|
3206
|
+
return JSON.stringify(payload);
|
|
3207
|
+
}
|
|
3208
|
+
function computeSeal(registry) {
|
|
3209
|
+
return import_crypto2.default.createHash("sha256").update(buildSealInput(registry)).digest("hex");
|
|
3210
|
+
}
|
|
3211
|
+
function sanitizeTrackedRepoEntry(input) {
|
|
3212
|
+
var _a2, _b, _c, _d, _e, _f;
|
|
3213
|
+
const id = String((_a2 = input.id) != null ? _a2 : "").trim();
|
|
3214
|
+
const rawPath = String((_b = input.path) != null ? _b : "").trim();
|
|
3215
|
+
if (!id || !rawPath) return null;
|
|
3216
|
+
return {
|
|
3217
|
+
id,
|
|
3218
|
+
name: String((_c = input.name) != null ? _c : "").trim(),
|
|
3219
|
+
path: normalizeRepoPath(rawPath),
|
|
3220
|
+
gitRemote: String((_d = input.gitRemote) != null ? _d : "").trim(),
|
|
3221
|
+
createdAt: String((_e = input.createdAt) != null ? _e : "").trim(),
|
|
3222
|
+
lastSeenAt: String((_f = input.lastSeenAt) != null ? _f : "").trim()
|
|
3223
|
+
};
|
|
3224
|
+
}
|
|
3225
|
+
function readInstallStateRaw() {
|
|
3226
|
+
var _a2, _b, _c;
|
|
3227
|
+
const p = getInstallStatePath();
|
|
3228
|
+
if (!import_fs8.default.existsSync(p)) return { exists: false, state: null };
|
|
3229
|
+
try {
|
|
3230
|
+
const parsed = JSON.parse(import_fs8.default.readFileSync(p, "utf-8"));
|
|
3231
|
+
const machineInstallId = String((_a2 = parsed.machineInstallId) != null ? _a2 : "").trim();
|
|
3232
|
+
if (!machineInstallId) return { exists: true, state: null };
|
|
3233
|
+
return {
|
|
3234
|
+
exists: true,
|
|
3235
|
+
state: {
|
|
3236
|
+
machineInstallId,
|
|
3237
|
+
createdAt: String((_b = parsed.createdAt) != null ? _b : "").trim() || nowIso(),
|
|
3238
|
+
updatedAt: String((_c = parsed.updatedAt) != null ? _c : "").trim() || nowIso()
|
|
3239
|
+
}
|
|
3240
|
+
};
|
|
3241
|
+
} catch (e) {
|
|
3242
|
+
return { exists: true, state: null };
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
function writeInstallState(state) {
|
|
3246
|
+
const dir = getGlobalRunTrimDir();
|
|
3247
|
+
if (!import_fs8.default.existsSync(dir)) import_fs8.default.mkdirSync(dir, { recursive: true });
|
|
3248
|
+
import_fs8.default.writeFileSync(getInstallStatePath(), JSON.stringify(state, null, 2), "utf-8");
|
|
3249
|
+
}
|
|
3250
|
+
function ensureInstallState() {
|
|
3251
|
+
const raw = readInstallStateRaw();
|
|
3252
|
+
if (raw.exists && raw.state) return raw.state;
|
|
3253
|
+
const created = {
|
|
3254
|
+
machineInstallId: randomId("rt_install"),
|
|
3255
|
+
createdAt: nowIso(),
|
|
3256
|
+
updatedAt: nowIso()
|
|
3257
|
+
};
|
|
3258
|
+
writeInstallState(created);
|
|
3259
|
+
return created;
|
|
3260
|
+
}
|
|
3261
|
+
function buildDefaultRegistry(install) {
|
|
3262
|
+
const base = {
|
|
3263
|
+
version: 2,
|
|
3264
|
+
stateVersion: 2,
|
|
3265
|
+
plan: "free",
|
|
3266
|
+
machineInstallId: install.machineInstallId,
|
|
3267
|
+
createdAt: nowIso(),
|
|
3268
|
+
updatedAt: nowIso(),
|
|
3269
|
+
trackedRepos: [],
|
|
3270
|
+
lastKnownRepo: null,
|
|
3271
|
+
telemetry: __spreadValues({}, EMPTY_TELEMETRY)
|
|
3272
|
+
};
|
|
3273
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3274
|
+
integrity: {
|
|
3275
|
+
algorithm: "sha256-local-seal-v1",
|
|
3276
|
+
seal: computeSeal(base)
|
|
3277
|
+
}
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
function saveRegistryWithSeal(registry) {
|
|
3281
|
+
var _a2;
|
|
3282
|
+
const dir = getGlobalRunTrimDir();
|
|
3283
|
+
if (!import_fs8.default.existsSync(dir)) import_fs8.default.mkdirSync(dir, { recursive: true });
|
|
3284
|
+
const normalizedBase = __spreadProps(__spreadValues({}, registry), {
|
|
3285
|
+
version: 2,
|
|
3286
|
+
stateVersion: 2,
|
|
3287
|
+
trackedRepos: registry.trackedRepos.map((r) => __spreadProps(__spreadValues({}, r), { path: normalizeRepoPath(r.path) })),
|
|
3288
|
+
telemetry: (_a2 = registry.telemetry) != null ? _a2 : __spreadValues({}, EMPTY_TELEMETRY)
|
|
3289
|
+
});
|
|
3290
|
+
const sealed = __spreadProps(__spreadValues({}, normalizedBase), {
|
|
3291
|
+
integrity: {
|
|
3292
|
+
algorithm: "sha256-local-seal-v1",
|
|
3293
|
+
seal: computeSeal(normalizedBase)
|
|
3294
|
+
}
|
|
3295
|
+
});
|
|
3296
|
+
import_fs8.default.writeFileSync(getGlobalRegistryPath(), JSON.stringify(sealed, null, 2), "utf-8");
|
|
3297
|
+
}
|
|
3298
|
+
function inspectGlobalRegistry() {
|
|
3299
|
+
var _a2, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
3300
|
+
const installRaw = readInstallStateRaw();
|
|
3301
|
+
const install = (_a2 = installRaw.state) != null ? _a2 : ensureInstallState();
|
|
3066
3302
|
const registryPath = getGlobalRegistryPath();
|
|
3067
|
-
|
|
3303
|
+
const defaultRegistry = buildDefaultRegistry(install);
|
|
3304
|
+
if (!import_fs8.default.existsSync(registryPath)) {
|
|
3305
|
+
if (installRaw.exists) {
|
|
3306
|
+
return {
|
|
3307
|
+
registry: defaultRegistry,
|
|
3308
|
+
needsRepair: true,
|
|
3309
|
+
repairReason: "missing_registry_after_initialization"
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
return { registry: defaultRegistry, needsRepair: false, repairReason: null };
|
|
3313
|
+
}
|
|
3068
3314
|
try {
|
|
3069
3315
|
const raw = JSON.parse(import_fs8.default.readFileSync(registryPath, "utf-8"));
|
|
3070
|
-
|
|
3071
|
-
|
|
3316
|
+
const trackedRepos = Array.isArray(raw.trackedRepos) ? raw.trackedRepos.map((item) => sanitizeTrackedRepoEntry(item)).filter((item) => Boolean(item)) : [];
|
|
3317
|
+
const base = {
|
|
3318
|
+
version: 2,
|
|
3319
|
+
stateVersion: 2,
|
|
3072
3320
|
plan: raw.plan === "free" ? "free" : "free",
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3321
|
+
machineInstallId: String((_b = raw.machineInstallId) != null ? _b : "").trim() || install.machineInstallId,
|
|
3322
|
+
createdAt: String((_c = raw.createdAt) != null ? _c : "").trim() || nowIso(),
|
|
3323
|
+
updatedAt: String((_d = raw.updatedAt) != null ? _d : "").trim() || nowIso(),
|
|
3324
|
+
trackedRepos,
|
|
3325
|
+
lastKnownRepo: raw.lastKnownRepo && typeof raw.lastKnownRepo === "object" ? {
|
|
3326
|
+
id: String((_e = raw.lastKnownRepo.id) != null ? _e : "").trim(),
|
|
3327
|
+
name: String((_f = raw.lastKnownRepo.name) != null ? _f : "").trim(),
|
|
3328
|
+
path: normalizeRepoPath(String((_g = raw.lastKnownRepo.path) != null ? _g : "")),
|
|
3329
|
+
gitRemote: String((_h = raw.lastKnownRepo.gitRemote) != null ? _h : "").trim(),
|
|
3330
|
+
lastSeenAt: String((_i = raw.lastKnownRepo.lastSeenAt) != null ? _i : "").trim() || nowIso()
|
|
3331
|
+
} : null,
|
|
3081
3332
|
telemetry: {
|
|
3082
3333
|
enabled: typeof raw.telemetry === "object" && raw.telemetry !== null && Boolean(raw.telemetry.enabled),
|
|
3083
3334
|
anonymousId: typeof raw.telemetry === "object" && raw.telemetry !== null && typeof raw.telemetry.anonymousId === "string" ? String(raw.telemetry.anonymousId).slice(0, 120) : ""
|
|
3084
3335
|
}
|
|
3085
3336
|
};
|
|
3337
|
+
const normalized = __spreadProps(__spreadValues({}, base), {
|
|
3338
|
+
integrity: {
|
|
3339
|
+
algorithm: "sha256-local-seal-v1",
|
|
3340
|
+
seal: raw.integrity && typeof raw.integrity === "object" && typeof raw.integrity.seal === "string" ? String(raw.integrity.seal) : ""
|
|
3341
|
+
}
|
|
3342
|
+
});
|
|
3343
|
+
if (normalized.machineInstallId !== install.machineInstallId) {
|
|
3344
|
+
return {
|
|
3345
|
+
registry: normalized,
|
|
3346
|
+
needsRepair: true,
|
|
3347
|
+
repairReason: "machine_install_id_mismatch"
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
const expectedSeal = computeSeal(base);
|
|
3351
|
+
if (!normalized.integrity.seal || normalized.integrity.seal !== expectedSeal) {
|
|
3352
|
+
return {
|
|
3353
|
+
registry: normalized,
|
|
3354
|
+
needsRepair: true,
|
|
3355
|
+
repairReason: "integrity_seal_mismatch"
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
return { registry: normalized, needsRepair: false, repairReason: null };
|
|
3086
3359
|
} catch (e) {
|
|
3087
|
-
return
|
|
3360
|
+
return {
|
|
3361
|
+
registry: defaultRegistry,
|
|
3362
|
+
needsRepair: true,
|
|
3363
|
+
repairReason: "registry_corrupt"
|
|
3364
|
+
};
|
|
3088
3365
|
}
|
|
3089
3366
|
}
|
|
3367
|
+
function loadGlobalRegistry() {
|
|
3368
|
+
return inspectGlobalRegistry().registry;
|
|
3369
|
+
}
|
|
3090
3370
|
function saveGlobalRegistry(registry) {
|
|
3091
|
-
|
|
3092
|
-
if (!import_fs8.default.existsSync(dir)) import_fs8.default.mkdirSync(dir, { recursive: true });
|
|
3093
|
-
import_fs8.default.writeFileSync(getGlobalRegistryPath(), JSON.stringify(registry, null, 2), "utf-8");
|
|
3371
|
+
saveRegistryWithSeal(registry);
|
|
3094
3372
|
}
|
|
3095
3373
|
async function getCurrentRepoIdentity(cwd = process.cwd()) {
|
|
3096
3374
|
const normalizedPath = normalizeRepoPath(cwd);
|
|
@@ -3120,36 +3398,71 @@ function findTrackedRepo(trackedRepos, currentRepo) {
|
|
|
3120
3398
|
return byPath != null ? byPath : null;
|
|
3121
3399
|
}
|
|
3122
3400
|
async function assertFreeRepoAllowed(cwd = process.cwd()) {
|
|
3123
|
-
const
|
|
3401
|
+
const inspected = inspectGlobalRegistry();
|
|
3402
|
+
const registry = inspected.registry;
|
|
3124
3403
|
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3125
3404
|
const trackedRepo = findTrackedRepo(registry.trackedRepos, currentRepo);
|
|
3126
|
-
|
|
3127
|
-
|
|
3405
|
+
const base = {
|
|
3406
|
+
plan: registry.plan,
|
|
3407
|
+
currentRepo,
|
|
3408
|
+
trackedRepo,
|
|
3409
|
+
registryPath: getGlobalRegistryPath()
|
|
3410
|
+
};
|
|
3411
|
+
if (inspected.needsRepair) {
|
|
3412
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3413
|
+
allowed: false,
|
|
3414
|
+
status: "blocked_repair",
|
|
3415
|
+
repairRequired: true,
|
|
3416
|
+
repairReason: inspected.repairReason
|
|
3417
|
+
});
|
|
3128
3418
|
}
|
|
3129
|
-
if (
|
|
3130
|
-
return {
|
|
3419
|
+
if (registry.plan !== "free") {
|
|
3420
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3421
|
+
allowed: true,
|
|
3422
|
+
status: "allowed",
|
|
3423
|
+
repairRequired: false,
|
|
3424
|
+
repairReason: null
|
|
3425
|
+
});
|
|
3131
3426
|
}
|
|
3132
|
-
if (registry.trackedRepos.length === 0) {
|
|
3133
|
-
return {
|
|
3427
|
+
if (trackedRepo || registry.trackedRepos.length === 0) {
|
|
3428
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3429
|
+
allowed: true,
|
|
3430
|
+
status: "allowed",
|
|
3431
|
+
repairRequired: false,
|
|
3432
|
+
repairReason: null
|
|
3433
|
+
});
|
|
3134
3434
|
}
|
|
3135
|
-
return {
|
|
3435
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3136
3436
|
allowed: false,
|
|
3137
|
-
|
|
3138
|
-
|
|
3437
|
+
status: "blocked_limit",
|
|
3438
|
+
repairRequired: false,
|
|
3439
|
+
repairReason: null,
|
|
3139
3440
|
trackedRepo: registry.trackedRepos[0]
|
|
3140
|
-
};
|
|
3441
|
+
});
|
|
3141
3442
|
}
|
|
3142
3443
|
async function registerCurrentRepo(cwd = process.cwd()) {
|
|
3444
|
+
const check = await assertFreeRepoAllowed(cwd);
|
|
3445
|
+
if (!check.allowed && check.status === "blocked_repair") {
|
|
3446
|
+
throw new Error("runtrim_local_state_repair_required");
|
|
3447
|
+
}
|
|
3143
3448
|
const registry = loadGlobalRegistry();
|
|
3144
3449
|
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3145
|
-
const now = (
|
|
3450
|
+
const now = nowIso();
|
|
3146
3451
|
const existing = findTrackedRepo(registry.trackedRepos, currentRepo);
|
|
3147
3452
|
if (existing) {
|
|
3148
3453
|
existing.lastSeenAt = now;
|
|
3149
3454
|
existing.name = currentRepo.name;
|
|
3150
3455
|
existing.path = currentRepo.path;
|
|
3151
3456
|
existing.gitRemote = currentRepo.gitRemote;
|
|
3152
|
-
|
|
3457
|
+
registry.updatedAt = now;
|
|
3458
|
+
registry.lastKnownRepo = {
|
|
3459
|
+
id: currentRepo.id,
|
|
3460
|
+
name: currentRepo.name,
|
|
3461
|
+
path: currentRepo.path,
|
|
3462
|
+
gitRemote: currentRepo.gitRemote,
|
|
3463
|
+
lastSeenAt: now
|
|
3464
|
+
};
|
|
3465
|
+
saveRegistryWithSeal(registry);
|
|
3153
3466
|
return existing;
|
|
3154
3467
|
}
|
|
3155
3468
|
const entry = {
|
|
@@ -3160,24 +3473,110 @@ async function registerCurrentRepo(cwd = process.cwd()) {
|
|
|
3160
3473
|
createdAt: now,
|
|
3161
3474
|
lastSeenAt: now
|
|
3162
3475
|
};
|
|
3163
|
-
registry.trackedRepos
|
|
3164
|
-
|
|
3476
|
+
registry.trackedRepos = [entry];
|
|
3477
|
+
registry.updatedAt = now;
|
|
3478
|
+
registry.lastKnownRepo = {
|
|
3479
|
+
id: entry.id,
|
|
3480
|
+
name: entry.name,
|
|
3481
|
+
path: entry.path,
|
|
3482
|
+
gitRemote: entry.gitRemote,
|
|
3483
|
+
lastSeenAt: now
|
|
3484
|
+
};
|
|
3485
|
+
saveRegistryWithSeal(registry);
|
|
3165
3486
|
return entry;
|
|
3166
3487
|
}
|
|
3488
|
+
async function repairGlobalRegistry(cwd = process.cwd(), options = {}) {
|
|
3489
|
+
const before = await assertFreeRepoAllowed(cwd);
|
|
3490
|
+
if (!before.repairRequired) {
|
|
3491
|
+
return { repaired: false, check: before };
|
|
3492
|
+
}
|
|
3493
|
+
const install = ensureInstallState();
|
|
3494
|
+
const now = nowIso();
|
|
3495
|
+
const repaired = buildDefaultRegistry(install);
|
|
3496
|
+
repaired.createdAt = now;
|
|
3497
|
+
repaired.updatedAt = now;
|
|
3498
|
+
if (options.useCurrentRepo) {
|
|
3499
|
+
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3500
|
+
repaired.trackedRepos = [
|
|
3501
|
+
{
|
|
3502
|
+
id: currentRepo.id,
|
|
3503
|
+
name: currentRepo.name,
|
|
3504
|
+
path: currentRepo.path,
|
|
3505
|
+
gitRemote: currentRepo.gitRemote,
|
|
3506
|
+
createdAt: now,
|
|
3507
|
+
lastSeenAt: now
|
|
3508
|
+
}
|
|
3509
|
+
];
|
|
3510
|
+
repaired.lastKnownRepo = {
|
|
3511
|
+
id: currentRepo.id,
|
|
3512
|
+
name: currentRepo.name,
|
|
3513
|
+
path: currentRepo.path,
|
|
3514
|
+
gitRemote: currentRepo.gitRemote,
|
|
3515
|
+
lastSeenAt: now
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3518
|
+
saveRegistryWithSeal(repaired);
|
|
3519
|
+
const check = await assertFreeRepoAllowed(cwd);
|
|
3520
|
+
return { repaired: true, check };
|
|
3521
|
+
}
|
|
3167
3522
|
async function unlinkCurrentRepo(cwd = process.cwd(), force = false) {
|
|
3168
3523
|
var _a2;
|
|
3524
|
+
const check = await assertFreeRepoAllowed(cwd);
|
|
3525
|
+
if (check.status === "blocked_repair") {
|
|
3526
|
+
if (!force) {
|
|
3527
|
+
return {
|
|
3528
|
+
removed: false,
|
|
3529
|
+
forced: false,
|
|
3530
|
+
currentRepo: check.currentRepo,
|
|
3531
|
+
trackedRepo: null
|
|
3532
|
+
};
|
|
3533
|
+
}
|
|
3534
|
+
const install = ensureInstallState();
|
|
3535
|
+
const repaired = buildDefaultRegistry(install);
|
|
3536
|
+
repaired.updatedAt = nowIso();
|
|
3537
|
+
repaired.lastKnownRepo = {
|
|
3538
|
+
id: check.currentRepo.id,
|
|
3539
|
+
name: check.currentRepo.name,
|
|
3540
|
+
path: check.currentRepo.path,
|
|
3541
|
+
gitRemote: check.currentRepo.gitRemote,
|
|
3542
|
+
lastSeenAt: repaired.updatedAt
|
|
3543
|
+
};
|
|
3544
|
+
saveRegistryWithSeal(repaired);
|
|
3545
|
+
return {
|
|
3546
|
+
removed: true,
|
|
3547
|
+
forced: true,
|
|
3548
|
+
currentRepo: check.currentRepo,
|
|
3549
|
+
trackedRepo: null
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3169
3552
|
const registry = loadGlobalRegistry();
|
|
3170
3553
|
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3171
3554
|
const trackedRepo = findTrackedRepo(registry.trackedRepos, currentRepo);
|
|
3172
3555
|
if (trackedRepo) {
|
|
3173
3556
|
registry.trackedRepos = registry.trackedRepos.filter((repo) => repo.id !== trackedRepo.id);
|
|
3174
|
-
|
|
3557
|
+
registry.updatedAt = nowIso();
|
|
3558
|
+
registry.lastKnownRepo = {
|
|
3559
|
+
id: trackedRepo.id,
|
|
3560
|
+
name: trackedRepo.name,
|
|
3561
|
+
path: trackedRepo.path,
|
|
3562
|
+
gitRemote: trackedRepo.gitRemote,
|
|
3563
|
+
lastSeenAt: registry.updatedAt
|
|
3564
|
+
};
|
|
3565
|
+
saveRegistryWithSeal(registry);
|
|
3175
3566
|
return { removed: true, forced: false, currentRepo, trackedRepo };
|
|
3176
3567
|
}
|
|
3177
3568
|
if (force && registry.trackedRepos.length > 0) {
|
|
3178
3569
|
const first = registry.trackedRepos[0];
|
|
3179
3570
|
registry.trackedRepos = [];
|
|
3180
|
-
|
|
3571
|
+
registry.updatedAt = nowIso();
|
|
3572
|
+
registry.lastKnownRepo = {
|
|
3573
|
+
id: first.id,
|
|
3574
|
+
name: first.name,
|
|
3575
|
+
path: first.path,
|
|
3576
|
+
gitRemote: first.gitRemote,
|
|
3577
|
+
lastSeenAt: registry.updatedAt
|
|
3578
|
+
};
|
|
3579
|
+
saveRegistryWithSeal(registry);
|
|
3181
3580
|
return { removed: true, forced: true, currentRepo, trackedRepo: first };
|
|
3182
3581
|
}
|
|
3183
3582
|
return {
|
|
@@ -4661,21 +5060,21 @@ var MEDIUM_PATH_PATTERNS = [
|
|
|
4661
5060
|
];
|
|
4662
5061
|
function classifyFileRisk(files) {
|
|
4663
5062
|
if (files.length === 0) return "low";
|
|
4664
|
-
let
|
|
5063
|
+
let maxRisk2 = "low";
|
|
4665
5064
|
for (const f of files) {
|
|
4666
5065
|
const norm = f.replace(/\\/g, "/").toLowerCase();
|
|
4667
5066
|
if (CRITICAL_PATH_PATTERNS.some((p) => norm.includes(p))) {
|
|
4668
5067
|
return "critical";
|
|
4669
5068
|
}
|
|
4670
|
-
if (HIGH_PATH_PATTERNS.some((p) => norm.includes(p)) &&
|
|
4671
|
-
|
|
5069
|
+
if (HIGH_PATH_PATTERNS.some((p) => norm.includes(p)) && maxRisk2 !== "high") {
|
|
5070
|
+
maxRisk2 = "high";
|
|
4672
5071
|
continue;
|
|
4673
5072
|
}
|
|
4674
|
-
if (MEDIUM_PATH_PATTERNS.some((p) => norm.includes(p)) &&
|
|
4675
|
-
|
|
5073
|
+
if (MEDIUM_PATH_PATTERNS.some((p) => norm.includes(p)) && maxRisk2 === "low") {
|
|
5074
|
+
maxRisk2 = "medium";
|
|
4676
5075
|
}
|
|
4677
5076
|
}
|
|
4678
|
-
return
|
|
5077
|
+
return maxRisk2;
|
|
4679
5078
|
}
|
|
4680
5079
|
function isSensitivePath(filePath) {
|
|
4681
5080
|
const norm = filePath.replace(/\\/g, "/").toLowerCase();
|
|
@@ -5018,6 +5417,24 @@ function getLearningContext(cwd, task, runs) {
|
|
|
5018
5417
|
// src/lib/run-planner.ts
|
|
5019
5418
|
var FAST_PATH_CATEGORIES = /* @__PURE__ */ new Set(["ui", "docs", "tests", "unknown"]);
|
|
5020
5419
|
var ALWAYS_CONTRACT_CATEGORIES = /* @__PURE__ */ new Set(["auth", "billing", "payment", "webhook", "database", "env", "middleware"]);
|
|
5420
|
+
var RISK_ORDER = ["low", "medium", "high", "critical"];
|
|
5421
|
+
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;
|
|
5422
|
+
function maxRisk(a, b) {
|
|
5423
|
+
return RISK_ORDER[Math.max(RISK_ORDER.indexOf(a), RISK_ORDER.indexOf(b))];
|
|
5424
|
+
}
|
|
5425
|
+
function hasNegationNear3(text, index) {
|
|
5426
|
+
const start = Math.max(0, index - 64);
|
|
5427
|
+
const window = text.slice(start, index + 8);
|
|
5428
|
+
return NEGATION_PREFIX_RE3.test(window);
|
|
5429
|
+
}
|
|
5430
|
+
function hasPositiveKeywordMention3(taskLower, keyword) {
|
|
5431
|
+
let idx = taskLower.indexOf(keyword.toLowerCase());
|
|
5432
|
+
while (idx !== -1) {
|
|
5433
|
+
if (!hasNegationNear3(taskLower, idx)) return true;
|
|
5434
|
+
idx = taskLower.indexOf(keyword.toLowerCase(), idx + keyword.length);
|
|
5435
|
+
}
|
|
5436
|
+
return false;
|
|
5437
|
+
}
|
|
5021
5438
|
function isFastPathEligible(risk, category, guardMode, hasExplicitPaths) {
|
|
5022
5439
|
if (guardMode === "strict") return false;
|
|
5023
5440
|
if (guardMode === "off") return true;
|
|
@@ -5033,8 +5450,16 @@ function generatePlan(cwd, task, runs, config, currentChangedFiles) {
|
|
|
5033
5450
|
const compiler = compileTask(task);
|
|
5034
5451
|
const guardMode = (_a2 = config.autoGuardMode) != null ? _a2 : "smart";
|
|
5035
5452
|
const adapters = detectAdapters(cwd);
|
|
5036
|
-
const riskFiles = compiler.explicitPaths.length > 0 ? compiler.explicitPaths :
|
|
5037
|
-
|
|
5453
|
+
const riskFiles = compiler.explicitPaths.length > 0 ? compiler.explicitPaths : [];
|
|
5454
|
+
let rawRisk = classifyFileRisk(riskFiles);
|
|
5455
|
+
if (ALWAYS_CONTRACT_CATEGORIES.has(compiler.taskCategory)) {
|
|
5456
|
+
rawRisk = maxRisk(rawRisk, "high");
|
|
5457
|
+
}
|
|
5458
|
+
const lowerTask = task.toLowerCase();
|
|
5459
|
+
const criticalSystemMentions = ["auth", "billing", "payment", "webhook", "database", "migration", "middleware"].filter((k) => hasPositiveKeywordMention3(lowerTask, k)).length;
|
|
5460
|
+
if (criticalSystemMentions >= 2) {
|
|
5461
|
+
rawRisk = maxRisk(rawRisk, "critical");
|
|
5462
|
+
}
|
|
5038
5463
|
const catScope = buildCategoryScope(
|
|
5039
5464
|
compiler.taskCategory,
|
|
5040
5465
|
true,
|
|
@@ -5158,6 +5583,22 @@ var FAST_LOCAL_KEYWORDS = [
|
|
|
5158
5583
|
"responsive",
|
|
5159
5584
|
"ui polish"
|
|
5160
5585
|
];
|
|
5586
|
+
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;
|
|
5587
|
+
function hasNegationNear4(text, index) {
|
|
5588
|
+
const start = Math.max(0, index - 64);
|
|
5589
|
+
const window = text.slice(start, index + 8);
|
|
5590
|
+
return NEGATION_PREFIX_RE4.test(window);
|
|
5591
|
+
}
|
|
5592
|
+
function hasPositiveKeywordMention4(task, keyword) {
|
|
5593
|
+
const lowerTask = task.toLowerCase();
|
|
5594
|
+
const lowerKeyword = keyword.toLowerCase();
|
|
5595
|
+
let idx = lowerTask.indexOf(lowerKeyword);
|
|
5596
|
+
while (idx !== -1) {
|
|
5597
|
+
if (!hasNegationNear4(lowerTask, idx)) return true;
|
|
5598
|
+
idx = lowerTask.indexOf(lowerKeyword, idx + lowerKeyword.length);
|
|
5599
|
+
}
|
|
5600
|
+
return false;
|
|
5601
|
+
}
|
|
5161
5602
|
function normalize(s) {
|
|
5162
5603
|
return (s != null ? s : "").toLowerCase();
|
|
5163
5604
|
}
|
|
@@ -5216,9 +5657,9 @@ function recommendProviderRouting(ctx) {
|
|
|
5216
5657
|
const changedFiles = (_d = ctx.changedFiles) != null ? _d : [];
|
|
5217
5658
|
const learnedContext = (_e = ctx.learnedContext) != null ? _e : [];
|
|
5218
5659
|
const hasProofGapSignals = proofRequired.some((p) => /proof gap|missing|vercel log|manual verification/i.test(p));
|
|
5219
|
-
const highRiskByKeyword = HIGH_RISK_KEYWORDS.some((k) => task
|
|
5660
|
+
const highRiskByKeyword = HIGH_RISK_KEYWORDS.some((k) => hasPositiveKeywordMention4(task, k) || hasPositiveKeywordMention4(category, k));
|
|
5220
5661
|
const fastKeyword = FAST_LOCAL_KEYWORDS.some((k) => task.includes(k));
|
|
5221
|
-
const multiCritical = ["auth", "billing", "database", "webhook", "payment", "migration"].filter((k) => task
|
|
5662
|
+
const multiCritical = ["auth", "billing", "database", "webhook", "payment", "migration"].filter((k) => hasPositiveKeywordMention4(task, k)).length >= 2;
|
|
5222
5663
|
const broadTask = !ctx.explicitScope && task.split(/\s+/).length > 16;
|
|
5223
5664
|
const hasSensitiveFiles = sensitiveAreas.length > 0 || changedFiles.some((f) => HIGH_RISK_KEYWORDS.some((k) => normalize(f).includes(k)));
|
|
5224
5665
|
const noLearning = learnedContext.length === 0 && ((_f = ctx.similarRunsCount) != null ? _f : 0) === 0;
|
|
@@ -5483,6 +5924,19 @@ async function getSensitivePathStates(cwd) {
|
|
|
5483
5924
|
return /* @__PURE__ */ new Map();
|
|
5484
5925
|
}
|
|
5485
5926
|
}
|
|
5927
|
+
function extractBoundaryLabels(forbiddenScope) {
|
|
5928
|
+
const labels = /* @__PURE__ */ new Set();
|
|
5929
|
+
for (const line of forbiddenScope) {
|
|
5930
|
+
const lower = line.toLowerCase();
|
|
5931
|
+
if (lower.includes("billing") || lower.includes("subscription") || lower.includes("payment")) labels.add("billing");
|
|
5932
|
+
if (lower.includes("auth") || lower.includes("session") || lower.includes("jwt")) labels.add("auth");
|
|
5933
|
+
if (lower.includes("middleware") || lower.includes("proxy")) labels.add("middleware");
|
|
5934
|
+
if (lower.includes(".env") || lower.includes("secret")) labels.add("env");
|
|
5935
|
+
if (lower.includes("cli")) labels.add("cli");
|
|
5936
|
+
if (lower.includes("mcp")) labels.add("mcp");
|
|
5937
|
+
}
|
|
5938
|
+
return [...labels];
|
|
5939
|
+
}
|
|
5486
5940
|
function nowId() {
|
|
5487
5941
|
const d = /* @__PURE__ */ new Date();
|
|
5488
5942
|
const pad = (n) => String(n).padStart(2, "0");
|
|
@@ -5544,8 +5998,21 @@ function buildApprovalLevel(risk, task, category) {
|
|
|
5544
5998
|
const text = `${task}
|
|
5545
5999
|
${category}`.toLowerCase();
|
|
5546
6000
|
const highSystems = ["auth", "billing", "payment", "dodo", "stripe", "webhook", "database", "migration", "rls", "middleware", "env", "secret"];
|
|
6001
|
+
const hasNegationNear5 = (source, index) => {
|
|
6002
|
+
const start = Math.max(0, index - 64);
|
|
6003
|
+
const window = source.slice(start, index + 8);
|
|
6004
|
+
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);
|
|
6005
|
+
};
|
|
6006
|
+
const hasPositiveKeyword = (source, keyword) => {
|
|
6007
|
+
let idx = source.indexOf(keyword);
|
|
6008
|
+
while (idx !== -1) {
|
|
6009
|
+
if (!hasNegationNear5(source, idx)) return true;
|
|
6010
|
+
idx = source.indexOf(keyword, idx + keyword.length);
|
|
6011
|
+
}
|
|
6012
|
+
return false;
|
|
6013
|
+
};
|
|
5547
6014
|
if (risk === "high" || risk === "critical") return "required";
|
|
5548
|
-
if (highSystems.some((k) => text
|
|
6015
|
+
if (highSystems.some((k) => hasPositiveKeyword(text, k))) return "required";
|
|
5549
6016
|
if (risk === "medium") return "recommended";
|
|
5550
6017
|
return "no";
|
|
5551
6018
|
}
|
|
@@ -5586,6 +6053,7 @@ function writePreviewArtifacts(cwd, preview) {
|
|
|
5586
6053
|
"",
|
|
5587
6054
|
"Forbidden:",
|
|
5588
6055
|
...preview.forbiddenScope.length > 0 ? preview.forbiddenScope.slice(0, 8).map((f) => `- ${f}`) : ["- none"],
|
|
6056
|
+
...preview.boundariesDetected.length > 0 ? ["", `Boundaries detected: ${preview.boundariesDetected.join(", ")} will be forbidden, not treated as active scope.`] : [],
|
|
5589
6057
|
"",
|
|
5590
6058
|
"Learned context:",
|
|
5591
6059
|
...preview.learnedContext.length > 0 ? preview.learnedContext.map((x) => `- ${x}`) : ["- learning not available yet"],
|
|
@@ -5625,6 +6093,9 @@ async function runAgentPreview(task) {
|
|
|
5625
6093
|
console.log("");
|
|
5626
6094
|
console.log(GO_ACCENT.bold("Forbidden"));
|
|
5627
6095
|
for (const item of preview.forbiddenScope.slice(0, 6)) console.log(DIM(" - ") + chalk.white(item));
|
|
6096
|
+
if (preview.boundariesDetected.length > 0) {
|
|
6097
|
+
console.log(DIM(" Boundaries detected: ") + chalk.white(`${preview.boundariesDetected.join(", ")} will be forbidden, not treated as active scope.`));
|
|
6098
|
+
}
|
|
5628
6099
|
console.log("");
|
|
5629
6100
|
console.log(GO_ACCENT.bold("Patch strategy"));
|
|
5630
6101
|
for (let i = 0; i < preview.patchStrategy.length; i += 1) {
|
|
@@ -5689,6 +6160,7 @@ async function buildAgentPreview(task) {
|
|
|
5689
6160
|
filesToInspect,
|
|
5690
6161
|
allowedScope: contract.contract.relevantScope,
|
|
5691
6162
|
forbiddenScope: contract.contract.forbiddenScope,
|
|
6163
|
+
boundariesDetected: extractBoundaryLabels(contract.contract.forbiddenScope),
|
|
5692
6164
|
sensitiveAreas: [...contract.contract.sensitiveScope, ...plan.sensitiveAreas].slice(0, 10),
|
|
5693
6165
|
stopRules: contract.contract.stopRules,
|
|
5694
6166
|
successCriteria: contract.contract.successCriteria,
|
|
@@ -7005,6 +7477,24 @@ async function buildRuntrimCreateContractMcp(cwd, args) {
|
|
|
7005
7477
|
isError: true
|
|
7006
7478
|
};
|
|
7007
7479
|
}
|
|
7480
|
+
const repoCheck = await assertFreeRepoAllowed(cwd);
|
|
7481
|
+
if (!repoCheck.allowed) {
|
|
7482
|
+
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.";
|
|
7483
|
+
const blockedPayload = {
|
|
7484
|
+
contract_created: false,
|
|
7485
|
+
task: taskRaw,
|
|
7486
|
+
error: repoCheck.status === "blocked_repair" ? "repo_registry_repair_required" : "repo_limit_blocked",
|
|
7487
|
+
guidance,
|
|
7488
|
+
next_action: guidance,
|
|
7489
|
+
finish_command: "runtrim finish",
|
|
7490
|
+
approval_command_example: 'runtrim approve "Allow <path> for this run only"'
|
|
7491
|
+
};
|
|
7492
|
+
return {
|
|
7493
|
+
content: [{ type: "text", text: JSON.stringify(blockedPayload, null, 2) }],
|
|
7494
|
+
structuredContent: blockedPayload,
|
|
7495
|
+
isError: true
|
|
7496
|
+
};
|
|
7497
|
+
}
|
|
7008
7498
|
const latest = loadLatestRun(cwd);
|
|
7009
7499
|
if ((latest == null ? void 0 : latest.status) === "guarded") {
|
|
7010
7500
|
const blockedPayload = {
|
|
@@ -7666,6 +8156,21 @@ function isInteractiveTerminal() {
|
|
|
7666
8156
|
async function ensureRepoAllowedForFree(cwd) {
|
|
7667
8157
|
var _a2, _b;
|
|
7668
8158
|
const check = await assertFreeRepoAllowed(cwd);
|
|
8159
|
+
if (check.status === "blocked_repair") {
|
|
8160
|
+
console.log(chalk.yellow(" RunTrim local state needs repair."));
|
|
8161
|
+
console.log(chalk.yellow(" Free includes 1 tracked repo."));
|
|
8162
|
+
console.log(chalk.yellow(" Your local repo registry changed unexpectedly."));
|
|
8163
|
+
console.log("");
|
|
8164
|
+
console.log(DIM(" Next:"));
|
|
8165
|
+
console.log(chalk.white(" - runtrim repo status"));
|
|
8166
|
+
console.log(chalk.white(" - runtrim repo repair"));
|
|
8167
|
+
console.log(chalk.white(" - runtrim repo repair --use-current"));
|
|
8168
|
+
console.log(chalk.white(" - runtrim repo unlink --force"));
|
|
8169
|
+
console.log(chalk.white(" - upgrade to Builder for unlimited repos"));
|
|
8170
|
+
console.log(chalk.white(" - sign in to restore cloud entitlements"));
|
|
8171
|
+
console.log("");
|
|
8172
|
+
return false;
|
|
8173
|
+
}
|
|
7669
8174
|
if (check.allowed) {
|
|
7670
8175
|
await registerCurrentRepo(cwd);
|
|
7671
8176
|
return true;
|
|
@@ -7679,9 +8184,12 @@ async function ensureRepoAllowedForFree(cwd) {
|
|
|
7679
8184
|
console.log(chalk.white(` ${check.currentRepo.path}`));
|
|
7680
8185
|
console.log("");
|
|
7681
8186
|
console.log(DIM(" Next:"));
|
|
7682
|
-
console.log(
|
|
7683
|
-
|
|
7684
|
-
|
|
8187
|
+
console.log(
|
|
8188
|
+
chalk.white(
|
|
8189
|
+
" 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."
|
|
8190
|
+
)
|
|
8191
|
+
);
|
|
8192
|
+
console.log(chalk.white(" Agent instructions were not installed because this repo is not tracked."));
|
|
7685
8193
|
console.log("");
|
|
7686
8194
|
console.log(
|
|
7687
8195
|
DIM(
|
|
@@ -8048,7 +8556,8 @@ var PROTOCOL_POINTER_BLOCK = `
|
|
|
8048
8556
|
${PROTOCOL_BLOCK_START}
|
|
8049
8557
|
This repo uses RunTrim as the guarded AI coding protocol.
|
|
8050
8558
|
Before editing code, read RUNTRIM.md.
|
|
8051
|
-
Start every task with: runtrim
|
|
8559
|
+
Start every task with: runtrim start
|
|
8560
|
+
Then run: runtrim agent "Your task" --copy
|
|
8052
8561
|
Stay inside .runtrim/contracts/latest.md.
|
|
8053
8562
|
After edits, ask the user to run: runtrim finish
|
|
8054
8563
|
${PROTOCOL_BLOCK_END}
|
|
@@ -8744,6 +9253,8 @@ program.command("execute <task>").description("Create a controlled execution pac
|
|
|
8744
9253
|
var agentCommand = program.command("agent").description("Start a guarded AI coding run with contract, scope, memory, and handoff");
|
|
8745
9254
|
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) => {
|
|
8746
9255
|
if (task == null ? void 0 : task.trim()) {
|
|
9256
|
+
const allowed = await ensureRepoAllowedForFree(process.cwd());
|
|
9257
|
+
if (!allowed) return;
|
|
8747
9258
|
const normalizedTask = (task != null ? task : "").trim();
|
|
8748
9259
|
if (options == null ? void 0 : options.bridge) {
|
|
8749
9260
|
const bridge = await ensureBridgeRunningForAgent(process.cwd());
|
|
@@ -9212,12 +9723,54 @@ repoCommand.command("status").description("Show local tracked repo status").acti
|
|
|
9212
9723
|
console.log(DIM(" Current repo ") + chalk.white(identity.path));
|
|
9213
9724
|
console.log(DIM(" Tracked repo ") + chalk.white((_b = tracked == null ? void 0 : tracked.path) != null ? _b : "(none)"));
|
|
9214
9725
|
console.log(DIM(" Allowed ") + chalk.white(check.allowed ? "yes" : "no"));
|
|
9726
|
+
console.log(DIM(" State ") + chalk.white(check.status));
|
|
9215
9727
|
console.log("");
|
|
9728
|
+
if (check.status === "blocked_repair") {
|
|
9729
|
+
console.log(chalk.yellow(" RunTrim local state needs repair."));
|
|
9730
|
+
console.log(chalk.yellow(" Free includes 1 tracked repo."));
|
|
9731
|
+
console.log(chalk.yellow(" The local repo registry changed unexpectedly."));
|
|
9732
|
+
console.log(DIM(" Run: runtrim repo repair"));
|
|
9733
|
+
console.log("");
|
|
9734
|
+
}
|
|
9216
9735
|
if (tracked) {
|
|
9217
9736
|
console.log(DIM(" A tracked repo is one codebase with its own .runtrim workspace."));
|
|
9218
9737
|
console.log("");
|
|
9219
9738
|
}
|
|
9220
9739
|
});
|
|
9740
|
+
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) => {
|
|
9741
|
+
const cwd = process.cwd();
|
|
9742
|
+
const before = await assertFreeRepoAllowed(cwd);
|
|
9743
|
+
console.log("");
|
|
9744
|
+
console.log(BOLD("RunTrim") + DIM(" repo repair"));
|
|
9745
|
+
console.log("");
|
|
9746
|
+
if (!before.repairRequired) {
|
|
9747
|
+
console.log(DIM(" Local state is healthy. No repair required."));
|
|
9748
|
+
console.log("");
|
|
9749
|
+
return;
|
|
9750
|
+
}
|
|
9751
|
+
if (!options.useCurrent) {
|
|
9752
|
+
console.log(chalk.yellow(" RunTrim local state needs repair."));
|
|
9753
|
+
console.log(chalk.yellow(" Free includes 1 tracked repo."));
|
|
9754
|
+
console.log(chalk.yellow(" Your local repo registry changed unexpectedly."));
|
|
9755
|
+
console.log("");
|
|
9756
|
+
console.log(DIM(" Safe next actions:"));
|
|
9757
|
+
console.log(chalk.white(" - runtrim repo repair --use-current"));
|
|
9758
|
+
console.log(chalk.white(" - runtrim repo unlink --force"));
|
|
9759
|
+
console.log(chalk.white(" - upgrade to Builder for unlimited repos"));
|
|
9760
|
+
console.log(chalk.white(" - sign in to restore cloud entitlements"));
|
|
9761
|
+
console.log("");
|
|
9762
|
+
return;
|
|
9763
|
+
}
|
|
9764
|
+
const result = await repairGlobalRegistry(cwd, { useCurrentRepo: true });
|
|
9765
|
+
if (result.repaired) {
|
|
9766
|
+
console.log(ACCENT.bold(" Local registry repaired."));
|
|
9767
|
+
console.log(DIM(" Current repo is now the tracked Free repo."));
|
|
9768
|
+
console.log("");
|
|
9769
|
+
return;
|
|
9770
|
+
}
|
|
9771
|
+
console.log(DIM(" No repair changes applied."));
|
|
9772
|
+
console.log("");
|
|
9773
|
+
});
|
|
9221
9774
|
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) => {
|
|
9222
9775
|
const cwd = process.cwd();
|
|
9223
9776
|
const result = await unlinkCurrentRepo(cwd, Boolean(options.force));
|
|
@@ -10384,7 +10937,7 @@ program.command("continue").description("Create a safe continuation prompt from
|
|
|
10384
10937
|
const audit = (_a2 = loadProjectAudit(cwd)) != null ? _a2 : performBaselineProjectAudit(cwd, null);
|
|
10385
10938
|
const reason = normalizeContinuationReason(options.reason);
|
|
10386
10939
|
const agent = normalizeContinuationAgent(options.agent, config.defaultAgent);
|
|
10387
|
-
const
|
|
10940
|
+
const nowIso2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
10388
10941
|
const continuationPath = resolveContinuationPath(cwd);
|
|
10389
10942
|
const latestPromptPath = resolvePromptPath(config, cwd);
|
|
10390
10943
|
const latestPrompt = import_fs13.default.existsSync(latestPromptPath) ? import_fs13.default.readFileSync(latestPromptPath, "utf-8").trim() : "";
|
|
@@ -10574,14 +11127,14 @@ program.command("continue").description("Create a safe continuation prompt from
|
|
|
10574
11127
|
import_fs13.default.writeFileSync(continuationPath, continuationPrompt, "utf-8");
|
|
10575
11128
|
const copied = await copyToClipboardSafe(continuationPrompt);
|
|
10576
11129
|
if (memory) {
|
|
10577
|
-
const memoryWithContinuation = updateMemoryWithContinuation(memory, reason, continuationPath,
|
|
11130
|
+
const memoryWithContinuation = updateMemoryWithContinuation(memory, reason, continuationPath, nowIso2);
|
|
10578
11131
|
import_fs13.default.writeFileSync(import_path13.default.join(getConfigDir(cwd), "memory.md"), memoryWithContinuation, "utf-8");
|
|
10579
11132
|
}
|
|
10580
11133
|
if (hasConfig) {
|
|
10581
11134
|
const nextConfig = __spreadProps(__spreadValues({}, config), {
|
|
10582
11135
|
lastContinuationReason: reason,
|
|
10583
11136
|
continuationPromptPath: continuationPath.replace(/\\/g, "/"),
|
|
10584
|
-
continuationCreatedAt:
|
|
11137
|
+
continuationCreatedAt: nowIso2
|
|
10585
11138
|
});
|
|
10586
11139
|
saveConfig(nextConfig, cwd);
|
|
10587
11140
|
}
|
|
@@ -11373,6 +11926,8 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
|
|
|
11373
11926
|
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) => {
|
|
11374
11927
|
var _a2, _b;
|
|
11375
11928
|
const cwd = process.cwd();
|
|
11929
|
+
const allowed = await ensureRepoAllowedForFree(cwd);
|
|
11930
|
+
if (!allowed) return;
|
|
11376
11931
|
console.log("");
|
|
11377
11932
|
console.log(BOLD("RunTrim") + DIM(" cloud sync"));
|
|
11378
11933
|
console.log("");
|