runtrim 0.1.17 → 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 +467 -57
- package/dist-cli/runtrim.js +467 -57
- package/package.json +1 -1
package/dist-cli/runtrim.js
CHANGED
|
@@ -666,6 +666,28 @@ function buildCategoryScope(category, hasSrc, hasApp, hasPages) {
|
|
|
666
666
|
"Check no regression in adjacent routes"
|
|
667
667
|
]
|
|
668
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
|
+
};
|
|
669
691
|
default:
|
|
670
692
|
return {
|
|
671
693
|
allowedHints: [],
|
|
@@ -2577,7 +2599,7 @@ function buildSyncPayload(input) {
|
|
|
2577
2599
|
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
|
|
2578
2600
|
const { cwd, projectName, config, projectAudit, memoryMarkdown, runs } = input;
|
|
2579
2601
|
const latest = runs[0];
|
|
2580
|
-
const
|
|
2602
|
+
const nowIso2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
2581
2603
|
const localProjectId = buildLocalProjectId(cwd);
|
|
2582
2604
|
const latestPromptText = readTextFileIfExists(path6.join(cwd, ".runtrim", "latest-prompt.md"));
|
|
2583
2605
|
const continuationPromptText = readTextFileIfExists(
|
|
@@ -2639,7 +2661,7 @@ function buildSyncPayload(input) {
|
|
|
2639
2661
|
name: resolveProjectName2(cwd, projectName, projectAudit == null ? void 0 : projectAudit.projectName),
|
|
2640
2662
|
stack: (_a2 = projectAudit == null ? void 0 : projectAudit.detectedStack) != null ? _a2 : config.stack ? config.stack.split(",").map((s) => s.trim()).filter(Boolean) : ["auto"],
|
|
2641
2663
|
packageManager: (_c = (_b = projectAudit == null ? void 0 : projectAudit.packageManager) != null ? _b : config.packageManager) != null ? _c : null,
|
|
2642
|
-
lastUpdated:
|
|
2664
|
+
lastUpdated: nowIso2
|
|
2643
2665
|
},
|
|
2644
2666
|
memory: {
|
|
2645
2667
|
markdown: memoryMarkdown,
|
|
@@ -2721,7 +2743,8 @@ function writeCanonicalRuntrimMd(cwd = process.cwd(), projectName) {
|
|
|
2721
2743
|
"## How to start an AI coding task",
|
|
2722
2744
|
"",
|
|
2723
2745
|
"```",
|
|
2724
|
-
|
|
2746
|
+
"runtrim start",
|
|
2747
|
+
'runtrim agent "Your task" --copy',
|
|
2725
2748
|
"```",
|
|
2726
2749
|
"",
|
|
2727
2750
|
"RunTrim creates a scoped contract, loads project memory, and generates a guarded prompt.",
|
|
@@ -2742,7 +2765,7 @@ function writeCanonicalRuntrimMd(cwd = process.cwd(), projectName) {
|
|
|
2742
2765
|
"",
|
|
2743
2766
|
"1. Read `.runtrim/contracts/latest.md`.",
|
|
2744
2767
|
" - If `Status: active` \u2014 a live task exists. Follow the contract strictly.",
|
|
2745
|
-
' - 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`.',
|
|
2746
2769
|
"2. Do not assume any prior task is still active.",
|
|
2747
2770
|
"3. Stay inside the allowed scope defined in the contract.",
|
|
2748
2771
|
"4. Stop and ask before touching any forbidden area.",
|
|
@@ -2767,7 +2790,8 @@ function writeRestingContract(cwd = process.cwd()) {
|
|
|
2767
2790
|
"Start one with:",
|
|
2768
2791
|
"",
|
|
2769
2792
|
"```",
|
|
2770
|
-
|
|
2793
|
+
"runtrim start",
|
|
2794
|
+
'runtrim agent "Your task" --copy',
|
|
2771
2795
|
"```",
|
|
2772
2796
|
"",
|
|
2773
2797
|
"---",
|
|
@@ -2790,7 +2814,8 @@ function writeRestingMemory(cwd = process.cwd()) {
|
|
|
2790
2814
|
"Start a new session with:",
|
|
2791
2815
|
"",
|
|
2792
2816
|
"```",
|
|
2793
|
-
|
|
2817
|
+
"runtrim start",
|
|
2818
|
+
'runtrim agent "Your task" --copy',
|
|
2794
2819
|
"```",
|
|
2795
2820
|
"",
|
|
2796
2821
|
"---",
|
|
@@ -2911,7 +2936,7 @@ function writeBridgeInstructions(cwd = process.cwd()) {
|
|
|
2911
2936
|
"1. Read `RUNTRIM.md`.",
|
|
2912
2937
|
"2. Read `.runtrim/contracts/latest.md`.",
|
|
2913
2938
|
" - If `Status: active` \u2014 follow the contract strictly.",
|
|
2914
|
-
' - 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`.',
|
|
2915
2940
|
"3. If the contract is active, read `.runtrim/memory/current.md` for session context.",
|
|
2916
2941
|
" If no active session, read `.runtrim/memory/baseline.md` for project baseline.",
|
|
2917
2942
|
"",
|
|
@@ -2985,6 +3010,28 @@ function buildBridgePrompt(contractText, ctx) {
|
|
|
2985
3010
|
// src/lib/run-watch.ts
|
|
2986
3011
|
function normalizeScopeKeywords2(scope) {
|
|
2987
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
|
+
]);
|
|
2988
3035
|
const words = /* @__PURE__ */ new Set();
|
|
2989
3036
|
for (const line of scope) {
|
|
2990
3037
|
const lower = line.toLowerCase();
|
|
@@ -2994,7 +3041,7 @@ function normalizeScopeKeywords2(scope) {
|
|
|
2994
3041
|
}
|
|
2995
3042
|
const cleaned = lower.replace(/[^a-z0-9_./\s-]/g, " ").split(/\s+/).filter(Boolean);
|
|
2996
3043
|
for (const token of cleaned) {
|
|
2997
|
-
if (token.length >= 4) words.add(token);
|
|
3044
|
+
if (token.length >= 4 && !genericStopwords.has(token)) words.add(token);
|
|
2998
3045
|
}
|
|
2999
3046
|
}
|
|
3000
3047
|
return [...words];
|
|
@@ -3091,15 +3138,7 @@ import fs8 from "fs";
|
|
|
3091
3138
|
import os from "os";
|
|
3092
3139
|
import path8 from "path";
|
|
3093
3140
|
import { execa as execa2 } from "execa";
|
|
3094
|
-
var
|
|
3095
|
-
version: 1,
|
|
3096
|
-
plan: "free",
|
|
3097
|
-
trackedRepos: [],
|
|
3098
|
-
telemetry: {
|
|
3099
|
-
enabled: false,
|
|
3100
|
-
anonymousId: ""
|
|
3101
|
-
}
|
|
3102
|
-
};
|
|
3141
|
+
var EMPTY_TELEMETRY = { enabled: false, anonymousId: "" };
|
|
3103
3142
|
function normalizeRepoPath(input) {
|
|
3104
3143
|
const resolved = path8.resolve(input);
|
|
3105
3144
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
@@ -3107,41 +3146,208 @@ function normalizeRepoPath(input) {
|
|
|
3107
3146
|
function hashValue(value) {
|
|
3108
3147
|
return crypto.createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
3109
3148
|
}
|
|
3149
|
+
function nowIso() {
|
|
3150
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3151
|
+
}
|
|
3152
|
+
function randomId(prefix) {
|
|
3153
|
+
return `${prefix}_${crypto.randomBytes(12).toString("hex")}`;
|
|
3154
|
+
}
|
|
3110
3155
|
function getGlobalRunTrimDir() {
|
|
3111
3156
|
return path8.join(os.homedir(), ".runtrim");
|
|
3112
3157
|
}
|
|
3113
3158
|
function getGlobalRegistryPath() {
|
|
3114
3159
|
return path8.join(getGlobalRunTrimDir(), "global.json");
|
|
3115
3160
|
}
|
|
3116
|
-
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();
|
|
3117
3281
|
const registryPath = getGlobalRegistryPath();
|
|
3118
|
-
|
|
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
|
+
}
|
|
3119
3293
|
try {
|
|
3120
3294
|
const raw = JSON.parse(fs8.readFileSync(registryPath, "utf-8"));
|
|
3121
|
-
|
|
3122
|
-
|
|
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,
|
|
3123
3299
|
plan: raw.plan === "free" ? "free" : "free",
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
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,
|
|
3132
3311
|
telemetry: {
|
|
3133
3312
|
enabled: typeof raw.telemetry === "object" && raw.telemetry !== null && Boolean(raw.telemetry.enabled),
|
|
3134
3313
|
anonymousId: typeof raw.telemetry === "object" && raw.telemetry !== null && typeof raw.telemetry.anonymousId === "string" ? String(raw.telemetry.anonymousId).slice(0, 120) : ""
|
|
3135
3314
|
}
|
|
3136
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 };
|
|
3137
3338
|
} catch (e) {
|
|
3138
|
-
return
|
|
3339
|
+
return {
|
|
3340
|
+
registry: defaultRegistry,
|
|
3341
|
+
needsRepair: true,
|
|
3342
|
+
repairReason: "registry_corrupt"
|
|
3343
|
+
};
|
|
3139
3344
|
}
|
|
3140
3345
|
}
|
|
3346
|
+
function loadGlobalRegistry() {
|
|
3347
|
+
return inspectGlobalRegistry().registry;
|
|
3348
|
+
}
|
|
3141
3349
|
function saveGlobalRegistry(registry) {
|
|
3142
|
-
|
|
3143
|
-
if (!fs8.existsSync(dir)) fs8.mkdirSync(dir, { recursive: true });
|
|
3144
|
-
fs8.writeFileSync(getGlobalRegistryPath(), JSON.stringify(registry, null, 2), "utf-8");
|
|
3350
|
+
saveRegistryWithSeal(registry);
|
|
3145
3351
|
}
|
|
3146
3352
|
async function getCurrentRepoIdentity(cwd = process.cwd()) {
|
|
3147
3353
|
const normalizedPath = normalizeRepoPath(cwd);
|
|
@@ -3171,36 +3377,71 @@ function findTrackedRepo(trackedRepos, currentRepo) {
|
|
|
3171
3377
|
return byPath != null ? byPath : null;
|
|
3172
3378
|
}
|
|
3173
3379
|
async function assertFreeRepoAllowed(cwd = process.cwd()) {
|
|
3174
|
-
const
|
|
3380
|
+
const inspected = inspectGlobalRegistry();
|
|
3381
|
+
const registry = inspected.registry;
|
|
3175
3382
|
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3176
3383
|
const trackedRepo = findTrackedRepo(registry.trackedRepos, currentRepo);
|
|
3177
|
-
|
|
3178
|
-
|
|
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
|
+
});
|
|
3179
3397
|
}
|
|
3180
|
-
if (
|
|
3181
|
-
return {
|
|
3398
|
+
if (registry.plan !== "free") {
|
|
3399
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3400
|
+
allowed: true,
|
|
3401
|
+
status: "allowed",
|
|
3402
|
+
repairRequired: false,
|
|
3403
|
+
repairReason: null
|
|
3404
|
+
});
|
|
3182
3405
|
}
|
|
3183
|
-
if (registry.trackedRepos.length === 0) {
|
|
3184
|
-
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
|
+
});
|
|
3185
3413
|
}
|
|
3186
|
-
return {
|
|
3414
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3187
3415
|
allowed: false,
|
|
3188
|
-
|
|
3189
|
-
|
|
3416
|
+
status: "blocked_limit",
|
|
3417
|
+
repairRequired: false,
|
|
3418
|
+
repairReason: null,
|
|
3190
3419
|
trackedRepo: registry.trackedRepos[0]
|
|
3191
|
-
};
|
|
3420
|
+
});
|
|
3192
3421
|
}
|
|
3193
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
|
+
}
|
|
3194
3427
|
const registry = loadGlobalRegistry();
|
|
3195
3428
|
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3196
|
-
const now = (
|
|
3429
|
+
const now = nowIso();
|
|
3197
3430
|
const existing = findTrackedRepo(registry.trackedRepos, currentRepo);
|
|
3198
3431
|
if (existing) {
|
|
3199
3432
|
existing.lastSeenAt = now;
|
|
3200
3433
|
existing.name = currentRepo.name;
|
|
3201
3434
|
existing.path = currentRepo.path;
|
|
3202
3435
|
existing.gitRemote = currentRepo.gitRemote;
|
|
3203
|
-
|
|
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);
|
|
3204
3445
|
return existing;
|
|
3205
3446
|
}
|
|
3206
3447
|
const entry = {
|
|
@@ -3211,24 +3452,110 @@ async function registerCurrentRepo(cwd = process.cwd()) {
|
|
|
3211
3452
|
createdAt: now,
|
|
3212
3453
|
lastSeenAt: now
|
|
3213
3454
|
};
|
|
3214
|
-
registry.trackedRepos
|
|
3215
|
-
|
|
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);
|
|
3216
3465
|
return entry;
|
|
3217
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
|
+
}
|
|
3218
3501
|
async function unlinkCurrentRepo(cwd = process.cwd(), force = false) {
|
|
3219
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
|
+
}
|
|
3220
3531
|
const registry = loadGlobalRegistry();
|
|
3221
3532
|
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3222
3533
|
const trackedRepo = findTrackedRepo(registry.trackedRepos, currentRepo);
|
|
3223
3534
|
if (trackedRepo) {
|
|
3224
3535
|
registry.trackedRepos = registry.trackedRepos.filter((repo) => repo.id !== trackedRepo.id);
|
|
3225
|
-
|
|
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);
|
|
3226
3545
|
return { removed: true, forced: false, currentRepo, trackedRepo };
|
|
3227
3546
|
}
|
|
3228
3547
|
if (force && registry.trackedRepos.length > 0) {
|
|
3229
3548
|
const first = registry.trackedRepos[0];
|
|
3230
3549
|
registry.trackedRepos = [];
|
|
3231
|
-
|
|
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);
|
|
3232
3559
|
return { removed: true, forced: true, currentRepo, trackedRepo: first };
|
|
3233
3560
|
}
|
|
3234
3561
|
return {
|
|
@@ -7129,6 +7456,24 @@ async function buildRuntrimCreateContractMcp(cwd, args) {
|
|
|
7129
7456
|
isError: true
|
|
7130
7457
|
};
|
|
7131
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
|
+
}
|
|
7132
7477
|
const latest = loadLatestRun(cwd);
|
|
7133
7478
|
if ((latest == null ? void 0 : latest.status) === "guarded") {
|
|
7134
7479
|
const blockedPayload = {
|
|
@@ -7790,6 +8135,21 @@ function isInteractiveTerminal() {
|
|
|
7790
8135
|
async function ensureRepoAllowedForFree(cwd) {
|
|
7791
8136
|
var _a2, _b;
|
|
7792
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
|
+
}
|
|
7793
8153
|
if (check.allowed) {
|
|
7794
8154
|
await registerCurrentRepo(cwd);
|
|
7795
8155
|
return true;
|
|
@@ -7803,9 +8163,12 @@ async function ensureRepoAllowedForFree(cwd) {
|
|
|
7803
8163
|
console.log(chalk.white(` ${check.currentRepo.path}`));
|
|
7804
8164
|
console.log("");
|
|
7805
8165
|
console.log(DIM(" Next:"));
|
|
7806
|
-
console.log(
|
|
7807
|
-
|
|
7808
|
-
|
|
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."));
|
|
7809
8172
|
console.log("");
|
|
7810
8173
|
console.log(
|
|
7811
8174
|
DIM(
|
|
@@ -8172,7 +8535,8 @@ var PROTOCOL_POINTER_BLOCK = `
|
|
|
8172
8535
|
${PROTOCOL_BLOCK_START}
|
|
8173
8536
|
This repo uses RunTrim as the guarded AI coding protocol.
|
|
8174
8537
|
Before editing code, read RUNTRIM.md.
|
|
8175
|
-
Start every task with: runtrim
|
|
8538
|
+
Start every task with: runtrim start
|
|
8539
|
+
Then run: runtrim agent "Your task" --copy
|
|
8176
8540
|
Stay inside .runtrim/contracts/latest.md.
|
|
8177
8541
|
After edits, ask the user to run: runtrim finish
|
|
8178
8542
|
${PROTOCOL_BLOCK_END}
|
|
@@ -8868,6 +9232,8 @@ program.command("execute <task>").description("Create a controlled execution pac
|
|
|
8868
9232
|
var agentCommand = program.command("agent").description("Start a guarded AI coding run with contract, scope, memory, and handoff");
|
|
8869
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) => {
|
|
8870
9234
|
if (task == null ? void 0 : task.trim()) {
|
|
9235
|
+
const allowed = await ensureRepoAllowedForFree(process.cwd());
|
|
9236
|
+
if (!allowed) return;
|
|
8871
9237
|
const normalizedTask = (task != null ? task : "").trim();
|
|
8872
9238
|
if (options == null ? void 0 : options.bridge) {
|
|
8873
9239
|
const bridge = await ensureBridgeRunningForAgent(process.cwd());
|
|
@@ -9336,12 +9702,54 @@ repoCommand.command("status").description("Show local tracked repo status").acti
|
|
|
9336
9702
|
console.log(DIM(" Current repo ") + chalk.white(identity.path));
|
|
9337
9703
|
console.log(DIM(" Tracked repo ") + chalk.white((_b = tracked == null ? void 0 : tracked.path) != null ? _b : "(none)"));
|
|
9338
9704
|
console.log(DIM(" Allowed ") + chalk.white(check.allowed ? "yes" : "no"));
|
|
9705
|
+
console.log(DIM(" State ") + chalk.white(check.status));
|
|
9339
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
|
+
}
|
|
9340
9714
|
if (tracked) {
|
|
9341
9715
|
console.log(DIM(" A tracked repo is one codebase with its own .runtrim workspace."));
|
|
9342
9716
|
console.log("");
|
|
9343
9717
|
}
|
|
9344
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
|
+
});
|
|
9345
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) => {
|
|
9346
9754
|
const cwd = process.cwd();
|
|
9347
9755
|
const result = await unlinkCurrentRepo(cwd, Boolean(options.force));
|
|
@@ -10508,7 +10916,7 @@ program.command("continue").description("Create a safe continuation prompt from
|
|
|
10508
10916
|
const audit = (_a2 = loadProjectAudit(cwd)) != null ? _a2 : performBaselineProjectAudit(cwd, null);
|
|
10509
10917
|
const reason = normalizeContinuationReason(options.reason);
|
|
10510
10918
|
const agent = normalizeContinuationAgent(options.agent, config.defaultAgent);
|
|
10511
|
-
const
|
|
10919
|
+
const nowIso2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
10512
10920
|
const continuationPath = resolveContinuationPath(cwd);
|
|
10513
10921
|
const latestPromptPath = resolvePromptPath(config, cwd);
|
|
10514
10922
|
const latestPrompt = fs13.existsSync(latestPromptPath) ? fs13.readFileSync(latestPromptPath, "utf-8").trim() : "";
|
|
@@ -10698,14 +11106,14 @@ program.command("continue").description("Create a safe continuation prompt from
|
|
|
10698
11106
|
fs13.writeFileSync(continuationPath, continuationPrompt, "utf-8");
|
|
10699
11107
|
const copied = await copyToClipboardSafe(continuationPrompt);
|
|
10700
11108
|
if (memory) {
|
|
10701
|
-
const memoryWithContinuation = updateMemoryWithContinuation(memory, reason, continuationPath,
|
|
11109
|
+
const memoryWithContinuation = updateMemoryWithContinuation(memory, reason, continuationPath, nowIso2);
|
|
10702
11110
|
fs13.writeFileSync(path13.join(getConfigDir(cwd), "memory.md"), memoryWithContinuation, "utf-8");
|
|
10703
11111
|
}
|
|
10704
11112
|
if (hasConfig) {
|
|
10705
11113
|
const nextConfig = __spreadProps(__spreadValues({}, config), {
|
|
10706
11114
|
lastContinuationReason: reason,
|
|
10707
11115
|
continuationPromptPath: continuationPath.replace(/\\/g, "/"),
|
|
10708
|
-
continuationCreatedAt:
|
|
11116
|
+
continuationCreatedAt: nowIso2
|
|
10709
11117
|
});
|
|
10710
11118
|
saveConfig(nextConfig, cwd);
|
|
10711
11119
|
}
|
|
@@ -11497,6 +11905,8 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
|
|
|
11497
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) => {
|
|
11498
11906
|
var _a2, _b;
|
|
11499
11907
|
const cwd = process.cwd();
|
|
11908
|
+
const allowed = await ensureRepoAllowedForFree(cwd);
|
|
11909
|
+
if (!allowed) return;
|
|
11500
11910
|
console.log("");
|
|
11501
11911
|
console.log(BOLD("RunTrim") + DIM(" cloud sync"));
|
|
11502
11912
|
console.log("");
|