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.cjs
CHANGED
|
@@ -687,6 +687,28 @@ function buildCategoryScope(category, hasSrc, hasApp, hasPages) {
|
|
|
687
687
|
"Check no regression in adjacent routes"
|
|
688
688
|
]
|
|
689
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
|
+
};
|
|
690
712
|
default:
|
|
691
713
|
return {
|
|
692
714
|
allowedHints: [],
|
|
@@ -2598,7 +2620,7 @@ function buildSyncPayload(input) {
|
|
|
2598
2620
|
var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t;
|
|
2599
2621
|
const { cwd, projectName, config, projectAudit, memoryMarkdown, runs } = input;
|
|
2600
2622
|
const latest = runs[0];
|
|
2601
|
-
const
|
|
2623
|
+
const nowIso2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
2602
2624
|
const localProjectId = buildLocalProjectId(cwd);
|
|
2603
2625
|
const latestPromptText = readTextFileIfExists(import_path6.default.join(cwd, ".runtrim", "latest-prompt.md"));
|
|
2604
2626
|
const continuationPromptText = readTextFileIfExists(
|
|
@@ -2660,7 +2682,7 @@ function buildSyncPayload(input) {
|
|
|
2660
2682
|
name: resolveProjectName2(cwd, projectName, projectAudit == null ? void 0 : projectAudit.projectName),
|
|
2661
2683
|
stack: (_a2 = projectAudit == null ? void 0 : projectAudit.detectedStack) != null ? _a2 : config.stack ? config.stack.split(",").map((s) => s.trim()).filter(Boolean) : ["auto"],
|
|
2662
2684
|
packageManager: (_c = (_b = projectAudit == null ? void 0 : projectAudit.packageManager) != null ? _b : config.packageManager) != null ? _c : null,
|
|
2663
|
-
lastUpdated:
|
|
2685
|
+
lastUpdated: nowIso2
|
|
2664
2686
|
},
|
|
2665
2687
|
memory: {
|
|
2666
2688
|
markdown: memoryMarkdown,
|
|
@@ -2742,7 +2764,8 @@ function writeCanonicalRuntrimMd(cwd = process.cwd(), projectName) {
|
|
|
2742
2764
|
"## How to start an AI coding task",
|
|
2743
2765
|
"",
|
|
2744
2766
|
"```",
|
|
2745
|
-
|
|
2767
|
+
"runtrim start",
|
|
2768
|
+
'runtrim agent "Your task" --copy',
|
|
2746
2769
|
"```",
|
|
2747
2770
|
"",
|
|
2748
2771
|
"RunTrim creates a scoped contract, loads project memory, and generates a guarded prompt.",
|
|
@@ -2763,7 +2786,7 @@ function writeCanonicalRuntrimMd(cwd = process.cwd(), projectName) {
|
|
|
2763
2786
|
"",
|
|
2764
2787
|
"1. Read `.runtrim/contracts/latest.md`.",
|
|
2765
2788
|
" - If `Status: active` \u2014 a live task exists. Follow the contract strictly.",
|
|
2766
|
-
' - 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`.',
|
|
2767
2790
|
"2. Do not assume any prior task is still active.",
|
|
2768
2791
|
"3. Stay inside the allowed scope defined in the contract.",
|
|
2769
2792
|
"4. Stop and ask before touching any forbidden area.",
|
|
@@ -2788,7 +2811,8 @@ function writeRestingContract(cwd = process.cwd()) {
|
|
|
2788
2811
|
"Start one with:",
|
|
2789
2812
|
"",
|
|
2790
2813
|
"```",
|
|
2791
|
-
|
|
2814
|
+
"runtrim start",
|
|
2815
|
+
'runtrim agent "Your task" --copy',
|
|
2792
2816
|
"```",
|
|
2793
2817
|
"",
|
|
2794
2818
|
"---",
|
|
@@ -2811,7 +2835,8 @@ function writeRestingMemory(cwd = process.cwd()) {
|
|
|
2811
2835
|
"Start a new session with:",
|
|
2812
2836
|
"",
|
|
2813
2837
|
"```",
|
|
2814
|
-
|
|
2838
|
+
"runtrim start",
|
|
2839
|
+
'runtrim agent "Your task" --copy',
|
|
2815
2840
|
"```",
|
|
2816
2841
|
"",
|
|
2817
2842
|
"---",
|
|
@@ -2932,7 +2957,7 @@ function writeBridgeInstructions(cwd = process.cwd()) {
|
|
|
2932
2957
|
"1. Read `RUNTRIM.md`.",
|
|
2933
2958
|
"2. Read `.runtrim/contracts/latest.md`.",
|
|
2934
2959
|
" - If `Status: active` \u2014 follow the contract strictly.",
|
|
2935
|
-
' - 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`.',
|
|
2936
2961
|
"3. If the contract is active, read `.runtrim/memory/current.md` for session context.",
|
|
2937
2962
|
" If no active session, read `.runtrim/memory/baseline.md` for project baseline.",
|
|
2938
2963
|
"",
|
|
@@ -3006,6 +3031,28 @@ function buildBridgePrompt(contractText, ctx) {
|
|
|
3006
3031
|
// src/lib/run-watch.ts
|
|
3007
3032
|
function normalizeScopeKeywords2(scope) {
|
|
3008
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
|
+
]);
|
|
3009
3056
|
const words = /* @__PURE__ */ new Set();
|
|
3010
3057
|
for (const line of scope) {
|
|
3011
3058
|
const lower = line.toLowerCase();
|
|
@@ -3015,7 +3062,7 @@ function normalizeScopeKeywords2(scope) {
|
|
|
3015
3062
|
}
|
|
3016
3063
|
const cleaned = lower.replace(/[^a-z0-9_./\s-]/g, " ").split(/\s+/).filter(Boolean);
|
|
3017
3064
|
for (const token of cleaned) {
|
|
3018
|
-
if (token.length >= 4) words.add(token);
|
|
3065
|
+
if (token.length >= 4 && !genericStopwords.has(token)) words.add(token);
|
|
3019
3066
|
}
|
|
3020
3067
|
}
|
|
3021
3068
|
return [...words];
|
|
@@ -3112,15 +3159,7 @@ var import_fs8 = __toESM(require("fs"), 1);
|
|
|
3112
3159
|
var import_os = __toESM(require("os"), 1);
|
|
3113
3160
|
var import_path8 = __toESM(require("path"), 1);
|
|
3114
3161
|
var import_execa2 = require("execa");
|
|
3115
|
-
var
|
|
3116
|
-
version: 1,
|
|
3117
|
-
plan: "free",
|
|
3118
|
-
trackedRepos: [],
|
|
3119
|
-
telemetry: {
|
|
3120
|
-
enabled: false,
|
|
3121
|
-
anonymousId: ""
|
|
3122
|
-
}
|
|
3123
|
-
};
|
|
3162
|
+
var EMPTY_TELEMETRY = { enabled: false, anonymousId: "" };
|
|
3124
3163
|
function normalizeRepoPath(input) {
|
|
3125
3164
|
const resolved = import_path8.default.resolve(input);
|
|
3126
3165
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
@@ -3128,41 +3167,208 @@ function normalizeRepoPath(input) {
|
|
|
3128
3167
|
function hashValue(value) {
|
|
3129
3168
|
return import_crypto2.default.createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
3130
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
|
+
}
|
|
3131
3176
|
function getGlobalRunTrimDir() {
|
|
3132
3177
|
return import_path8.default.join(import_os.default.homedir(), ".runtrim");
|
|
3133
3178
|
}
|
|
3134
3179
|
function getGlobalRegistryPath() {
|
|
3135
3180
|
return import_path8.default.join(getGlobalRunTrimDir(), "global.json");
|
|
3136
3181
|
}
|
|
3137
|
-
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();
|
|
3138
3302
|
const registryPath = getGlobalRegistryPath();
|
|
3139
|
-
|
|
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
|
+
}
|
|
3140
3314
|
try {
|
|
3141
3315
|
const raw = JSON.parse(import_fs8.default.readFileSync(registryPath, "utf-8"));
|
|
3142
|
-
|
|
3143
|
-
|
|
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,
|
|
3144
3320
|
plan: raw.plan === "free" ? "free" : "free",
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
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,
|
|
3153
3332
|
telemetry: {
|
|
3154
3333
|
enabled: typeof raw.telemetry === "object" && raw.telemetry !== null && Boolean(raw.telemetry.enabled),
|
|
3155
3334
|
anonymousId: typeof raw.telemetry === "object" && raw.telemetry !== null && typeof raw.telemetry.anonymousId === "string" ? String(raw.telemetry.anonymousId).slice(0, 120) : ""
|
|
3156
3335
|
}
|
|
3157
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 };
|
|
3158
3359
|
} catch (e) {
|
|
3159
|
-
return
|
|
3360
|
+
return {
|
|
3361
|
+
registry: defaultRegistry,
|
|
3362
|
+
needsRepair: true,
|
|
3363
|
+
repairReason: "registry_corrupt"
|
|
3364
|
+
};
|
|
3160
3365
|
}
|
|
3161
3366
|
}
|
|
3367
|
+
function loadGlobalRegistry() {
|
|
3368
|
+
return inspectGlobalRegistry().registry;
|
|
3369
|
+
}
|
|
3162
3370
|
function saveGlobalRegistry(registry) {
|
|
3163
|
-
|
|
3164
|
-
if (!import_fs8.default.existsSync(dir)) import_fs8.default.mkdirSync(dir, { recursive: true });
|
|
3165
|
-
import_fs8.default.writeFileSync(getGlobalRegistryPath(), JSON.stringify(registry, null, 2), "utf-8");
|
|
3371
|
+
saveRegistryWithSeal(registry);
|
|
3166
3372
|
}
|
|
3167
3373
|
async function getCurrentRepoIdentity(cwd = process.cwd()) {
|
|
3168
3374
|
const normalizedPath = normalizeRepoPath(cwd);
|
|
@@ -3192,36 +3398,71 @@ function findTrackedRepo(trackedRepos, currentRepo) {
|
|
|
3192
3398
|
return byPath != null ? byPath : null;
|
|
3193
3399
|
}
|
|
3194
3400
|
async function assertFreeRepoAllowed(cwd = process.cwd()) {
|
|
3195
|
-
const
|
|
3401
|
+
const inspected = inspectGlobalRegistry();
|
|
3402
|
+
const registry = inspected.registry;
|
|
3196
3403
|
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3197
3404
|
const trackedRepo = findTrackedRepo(registry.trackedRepos, currentRepo);
|
|
3198
|
-
|
|
3199
|
-
|
|
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
|
+
});
|
|
3200
3418
|
}
|
|
3201
|
-
if (
|
|
3202
|
-
return {
|
|
3419
|
+
if (registry.plan !== "free") {
|
|
3420
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3421
|
+
allowed: true,
|
|
3422
|
+
status: "allowed",
|
|
3423
|
+
repairRequired: false,
|
|
3424
|
+
repairReason: null
|
|
3425
|
+
});
|
|
3203
3426
|
}
|
|
3204
|
-
if (registry.trackedRepos.length === 0) {
|
|
3205
|
-
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
|
+
});
|
|
3206
3434
|
}
|
|
3207
|
-
return {
|
|
3435
|
+
return __spreadProps(__spreadValues({}, base), {
|
|
3208
3436
|
allowed: false,
|
|
3209
|
-
|
|
3210
|
-
|
|
3437
|
+
status: "blocked_limit",
|
|
3438
|
+
repairRequired: false,
|
|
3439
|
+
repairReason: null,
|
|
3211
3440
|
trackedRepo: registry.trackedRepos[0]
|
|
3212
|
-
};
|
|
3441
|
+
});
|
|
3213
3442
|
}
|
|
3214
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
|
+
}
|
|
3215
3448
|
const registry = loadGlobalRegistry();
|
|
3216
3449
|
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3217
|
-
const now = (
|
|
3450
|
+
const now = nowIso();
|
|
3218
3451
|
const existing = findTrackedRepo(registry.trackedRepos, currentRepo);
|
|
3219
3452
|
if (existing) {
|
|
3220
3453
|
existing.lastSeenAt = now;
|
|
3221
3454
|
existing.name = currentRepo.name;
|
|
3222
3455
|
existing.path = currentRepo.path;
|
|
3223
3456
|
existing.gitRemote = currentRepo.gitRemote;
|
|
3224
|
-
|
|
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);
|
|
3225
3466
|
return existing;
|
|
3226
3467
|
}
|
|
3227
3468
|
const entry = {
|
|
@@ -3232,24 +3473,110 @@ async function registerCurrentRepo(cwd = process.cwd()) {
|
|
|
3232
3473
|
createdAt: now,
|
|
3233
3474
|
lastSeenAt: now
|
|
3234
3475
|
};
|
|
3235
|
-
registry.trackedRepos
|
|
3236
|
-
|
|
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);
|
|
3237
3486
|
return entry;
|
|
3238
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
|
+
}
|
|
3239
3522
|
async function unlinkCurrentRepo(cwd = process.cwd(), force = false) {
|
|
3240
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
|
+
}
|
|
3241
3552
|
const registry = loadGlobalRegistry();
|
|
3242
3553
|
const currentRepo = await getCurrentRepoIdentity(cwd);
|
|
3243
3554
|
const trackedRepo = findTrackedRepo(registry.trackedRepos, currentRepo);
|
|
3244
3555
|
if (trackedRepo) {
|
|
3245
3556
|
registry.trackedRepos = registry.trackedRepos.filter((repo) => repo.id !== trackedRepo.id);
|
|
3246
|
-
|
|
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);
|
|
3247
3566
|
return { removed: true, forced: false, currentRepo, trackedRepo };
|
|
3248
3567
|
}
|
|
3249
3568
|
if (force && registry.trackedRepos.length > 0) {
|
|
3250
3569
|
const first = registry.trackedRepos[0];
|
|
3251
3570
|
registry.trackedRepos = [];
|
|
3252
|
-
|
|
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);
|
|
3253
3580
|
return { removed: true, forced: true, currentRepo, trackedRepo: first };
|
|
3254
3581
|
}
|
|
3255
3582
|
return {
|
|
@@ -7150,6 +7477,24 @@ async function buildRuntrimCreateContractMcp(cwd, args) {
|
|
|
7150
7477
|
isError: true
|
|
7151
7478
|
};
|
|
7152
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
|
+
}
|
|
7153
7498
|
const latest = loadLatestRun(cwd);
|
|
7154
7499
|
if ((latest == null ? void 0 : latest.status) === "guarded") {
|
|
7155
7500
|
const blockedPayload = {
|
|
@@ -7811,6 +8156,21 @@ function isInteractiveTerminal() {
|
|
|
7811
8156
|
async function ensureRepoAllowedForFree(cwd) {
|
|
7812
8157
|
var _a2, _b;
|
|
7813
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
|
+
}
|
|
7814
8174
|
if (check.allowed) {
|
|
7815
8175
|
await registerCurrentRepo(cwd);
|
|
7816
8176
|
return true;
|
|
@@ -7824,9 +8184,12 @@ async function ensureRepoAllowedForFree(cwd) {
|
|
|
7824
8184
|
console.log(chalk.white(` ${check.currentRepo.path}`));
|
|
7825
8185
|
console.log("");
|
|
7826
8186
|
console.log(DIM(" Next:"));
|
|
7827
|
-
console.log(
|
|
7828
|
-
|
|
7829
|
-
|
|
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."));
|
|
7830
8193
|
console.log("");
|
|
7831
8194
|
console.log(
|
|
7832
8195
|
DIM(
|
|
@@ -8193,7 +8556,8 @@ var PROTOCOL_POINTER_BLOCK = `
|
|
|
8193
8556
|
${PROTOCOL_BLOCK_START}
|
|
8194
8557
|
This repo uses RunTrim as the guarded AI coding protocol.
|
|
8195
8558
|
Before editing code, read RUNTRIM.md.
|
|
8196
|
-
Start every task with: runtrim
|
|
8559
|
+
Start every task with: runtrim start
|
|
8560
|
+
Then run: runtrim agent "Your task" --copy
|
|
8197
8561
|
Stay inside .runtrim/contracts/latest.md.
|
|
8198
8562
|
After edits, ask the user to run: runtrim finish
|
|
8199
8563
|
${PROTOCOL_BLOCK_END}
|
|
@@ -8889,6 +9253,8 @@ program.command("execute <task>").description("Create a controlled execution pac
|
|
|
8889
9253
|
var agentCommand = program.command("agent").description("Start a guarded AI coding run with contract, scope, memory, and handoff");
|
|
8890
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) => {
|
|
8891
9255
|
if (task == null ? void 0 : task.trim()) {
|
|
9256
|
+
const allowed = await ensureRepoAllowedForFree(process.cwd());
|
|
9257
|
+
if (!allowed) return;
|
|
8892
9258
|
const normalizedTask = (task != null ? task : "").trim();
|
|
8893
9259
|
if (options == null ? void 0 : options.bridge) {
|
|
8894
9260
|
const bridge = await ensureBridgeRunningForAgent(process.cwd());
|
|
@@ -9357,12 +9723,54 @@ repoCommand.command("status").description("Show local tracked repo status").acti
|
|
|
9357
9723
|
console.log(DIM(" Current repo ") + chalk.white(identity.path));
|
|
9358
9724
|
console.log(DIM(" Tracked repo ") + chalk.white((_b = tracked == null ? void 0 : tracked.path) != null ? _b : "(none)"));
|
|
9359
9725
|
console.log(DIM(" Allowed ") + chalk.white(check.allowed ? "yes" : "no"));
|
|
9726
|
+
console.log(DIM(" State ") + chalk.white(check.status));
|
|
9360
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
|
+
}
|
|
9361
9735
|
if (tracked) {
|
|
9362
9736
|
console.log(DIM(" A tracked repo is one codebase with its own .runtrim workspace."));
|
|
9363
9737
|
console.log("");
|
|
9364
9738
|
}
|
|
9365
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
|
+
});
|
|
9366
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) => {
|
|
9367
9775
|
const cwd = process.cwd();
|
|
9368
9776
|
const result = await unlinkCurrentRepo(cwd, Boolean(options.force));
|
|
@@ -10529,7 +10937,7 @@ program.command("continue").description("Create a safe continuation prompt from
|
|
|
10529
10937
|
const audit = (_a2 = loadProjectAudit(cwd)) != null ? _a2 : performBaselineProjectAudit(cwd, null);
|
|
10530
10938
|
const reason = normalizeContinuationReason(options.reason);
|
|
10531
10939
|
const agent = normalizeContinuationAgent(options.agent, config.defaultAgent);
|
|
10532
|
-
const
|
|
10940
|
+
const nowIso2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
10533
10941
|
const continuationPath = resolveContinuationPath(cwd);
|
|
10534
10942
|
const latestPromptPath = resolvePromptPath(config, cwd);
|
|
10535
10943
|
const latestPrompt = import_fs13.default.existsSync(latestPromptPath) ? import_fs13.default.readFileSync(latestPromptPath, "utf-8").trim() : "";
|
|
@@ -10719,14 +11127,14 @@ program.command("continue").description("Create a safe continuation prompt from
|
|
|
10719
11127
|
import_fs13.default.writeFileSync(continuationPath, continuationPrompt, "utf-8");
|
|
10720
11128
|
const copied = await copyToClipboardSafe(continuationPrompt);
|
|
10721
11129
|
if (memory) {
|
|
10722
|
-
const memoryWithContinuation = updateMemoryWithContinuation(memory, reason, continuationPath,
|
|
11130
|
+
const memoryWithContinuation = updateMemoryWithContinuation(memory, reason, continuationPath, nowIso2);
|
|
10723
11131
|
import_fs13.default.writeFileSync(import_path13.default.join(getConfigDir(cwd), "memory.md"), memoryWithContinuation, "utf-8");
|
|
10724
11132
|
}
|
|
10725
11133
|
if (hasConfig) {
|
|
10726
11134
|
const nextConfig = __spreadProps(__spreadValues({}, config), {
|
|
10727
11135
|
lastContinuationReason: reason,
|
|
10728
11136
|
continuationPromptPath: continuationPath.replace(/\\/g, "/"),
|
|
10729
|
-
continuationCreatedAt:
|
|
11137
|
+
continuationCreatedAt: nowIso2
|
|
10730
11138
|
});
|
|
10731
11139
|
saveConfig(nextConfig, cwd);
|
|
10732
11140
|
}
|
|
@@ -11518,6 +11926,8 @@ program.command("finish").description("Bridge Mode: evaluate agent output, check
|
|
|
11518
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) => {
|
|
11519
11927
|
var _a2, _b;
|
|
11520
11928
|
const cwd = process.cwd();
|
|
11929
|
+
const allowed = await ensureRepoAllowedForFree(cwd);
|
|
11930
|
+
if (!allowed) return;
|
|
11521
11931
|
console.log("");
|
|
11522
11932
|
console.log(BOLD("RunTrim") + DIM(" cloud sync"));
|
|
11523
11933
|
console.log("");
|