@dadado/agent-kit-cli 5.11.0 → 5.12.1
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/dashboard/dashboard-data.mjs +3 -1
- package/dashboard/lib/semantic-model.mjs +5 -4
- package/dist/index.js +537 -564
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,12 +10,15 @@ import { defineCommand } from "citty";
|
|
|
10
10
|
import { copyFile, mkdir as mkdir3, readFile as readFile3 } from "fs/promises";
|
|
11
11
|
import path4 from "path";
|
|
12
12
|
|
|
13
|
+
// src/utils/kit-paths.ts
|
|
14
|
+
var HANDOFF_REL = ".cursor/HANDOFF.md";
|
|
15
|
+
|
|
13
16
|
// src/manifest/types.ts
|
|
14
17
|
var MANIFEST_FILENAME = "agent-kit.json";
|
|
15
18
|
var MANIFEST_RELATIVE_PATH = `.cursor/${MANIFEST_FILENAME}`;
|
|
16
19
|
var MANIFEST_SCHEMA_VERSION = 1;
|
|
17
20
|
var DEFAULT_PROTECTED_PATHS = [
|
|
18
|
-
|
|
21
|
+
HANDOFF_REL,
|
|
19
22
|
".cursor/plans/**",
|
|
20
23
|
".cursor/memory/**",
|
|
21
24
|
".cursor/context/config.json",
|
|
@@ -347,7 +350,11 @@ var KNOWN_SHIPPED_OVERLAY_HASHES = /* @__PURE__ */ new Set([
|
|
|
347
350
|
"73e3ca429cb522b0877f0d6d46bedbaf270f94bc63bb87f4e0fc7c512bf36e9c",
|
|
348
351
|
"9a61f7432cf66be53d42b5d6f3c67ea71dd5efa1cc18d77d996f08ffea873373",
|
|
349
352
|
"fd2a8867de62f83c89ae11295cae5b5699360b5205f9726c2c47c064318bd7a4",
|
|
350
|
-
"364126473e089ed09a71e3f9eed80b1d602b35b4baa669b0a4f4be0519a787f8"
|
|
353
|
+
"364126473e089ed09a71e3f9eed80b1d602b35b4baa669b0a4f4be0519a787f8",
|
|
354
|
+
"cb14c0162e66ac565679089a566a725b26989db3536b7dba89bff77cb6010969",
|
|
355
|
+
"ebf490086ea45a7b342b3ec7408c9a82b22031daaefddf4425b6e6e931f6d6c8",
|
|
356
|
+
"3077e013297c7dd1e32eb2fa86e1ffa1de7d8740058652782bb8d391782eed71",
|
|
357
|
+
"7e0fa1bb73c8b6f1358cae19477304d85edfb7d4d9a7faf37c00e0578ed79edf"
|
|
351
358
|
]);
|
|
352
359
|
|
|
353
360
|
// src/lifecycle/paths.ts
|
|
@@ -718,6 +725,11 @@ import { mkdir as mkdir4, readFile as readFile4, rename, rm, stat, writeFile as
|
|
|
718
725
|
import { homedir } from "os";
|
|
719
726
|
import path5 from "path";
|
|
720
727
|
import { promisify } from "util";
|
|
728
|
+
|
|
729
|
+
// src/registry/paths.ts
|
|
730
|
+
var REGISTRY_INDEX_REL = "registry/registry.json";
|
|
731
|
+
|
|
732
|
+
// src/registry/resolve.ts
|
|
721
733
|
var execFileAsync = promisify(execFile);
|
|
722
734
|
var LOCK_TIMEOUT_MS = 6e4;
|
|
723
735
|
var LOCK_RETRY_MS = 200;
|
|
@@ -876,7 +888,7 @@ function cacheKey(url, ref) {
|
|
|
876
888
|
return createHash2("sha256").update(`${url}@${ref}`).digest("hex").slice(0, 16);
|
|
877
889
|
}
|
|
878
890
|
async function hasRegistryIndex(root) {
|
|
879
|
-
return fileExists(path5.join(root,
|
|
891
|
+
return fileExists(path5.join(root, REGISTRY_INDEX_REL));
|
|
880
892
|
}
|
|
881
893
|
async function cloneRegistry(url, ref, dest) {
|
|
882
894
|
const { mkdir: mkdir9 } = await import("fs/promises");
|
|
@@ -924,7 +936,7 @@ async function resolveRegistryRoot(options) {
|
|
|
924
936
|
if (options.registryPath) {
|
|
925
937
|
const root = path5.resolve(options.registryPath);
|
|
926
938
|
if (!await hasRegistryIndex(root)) {
|
|
927
|
-
throw new Error(`No
|
|
939
|
+
throw new Error(`No ${REGISTRY_INDEX_REL} under --registry ${root}`);
|
|
928
940
|
}
|
|
929
941
|
return { root, source: "flag" };
|
|
930
942
|
}
|
|
@@ -932,7 +944,7 @@ async function resolveRegistryRoot(options) {
|
|
|
932
944
|
if (envPath) {
|
|
933
945
|
const root = path5.resolve(envPath);
|
|
934
946
|
if (!await hasRegistryIndex(root)) {
|
|
935
|
-
throw new Error(`No
|
|
947
|
+
throw new Error(`No ${REGISTRY_INDEX_REL} under AGENT_KIT_REGISTRY=${root}`);
|
|
936
948
|
}
|
|
937
949
|
return { root, source: "env", url: options.registryUrl, ref: options.registryRef };
|
|
938
950
|
}
|
|
@@ -956,7 +968,7 @@ async function resolveRegistryRoot(options) {
|
|
|
956
968
|
} else {
|
|
957
969
|
await cloneRegistry(url, ref, dest);
|
|
958
970
|
if (!await hasRegistryIndex(dest)) {
|
|
959
|
-
throw new Error(`Cloned ${url}@${ref} but
|
|
971
|
+
throw new Error(`Cloned ${url}@${ref} but ${REGISTRY_INDEX_REL} is missing`);
|
|
960
972
|
}
|
|
961
973
|
}
|
|
962
974
|
ownershipTransferred = true;
|
|
@@ -1206,7 +1218,7 @@ async function loadAgentKitManifest(cwd) {
|
|
|
1206
1218
|
// src/registry/client.ts
|
|
1207
1219
|
import path7 from "path";
|
|
1208
1220
|
async function loadRegistry(rootDir) {
|
|
1209
|
-
const indexPath = path7.join(rootDir,
|
|
1221
|
+
const indexPath = path7.join(rootDir, REGISTRY_INDEX_REL);
|
|
1210
1222
|
const index = await readJson(indexPath);
|
|
1211
1223
|
if (!index) throw new Error(`Registry not found at ${indexPath}`);
|
|
1212
1224
|
return index;
|
|
@@ -1323,7 +1335,7 @@ async function installSkill(registryRoot, projectRoot, skill, options = {}) {
|
|
|
1323
1335
|
return stats;
|
|
1324
1336
|
}
|
|
1325
1337
|
async function installSkillsByIds(registryRoot, projectRoot, skillIds, options = {}) {
|
|
1326
|
-
const index = await readJson(path8.join(registryRoot,
|
|
1338
|
+
const index = await readJson(path8.join(registryRoot, REGISTRY_INDEX_REL));
|
|
1327
1339
|
const stats = emptyStats();
|
|
1328
1340
|
if (!index) return stats;
|
|
1329
1341
|
const pool = allSkills(index);
|
|
@@ -2122,22 +2134,40 @@ import { defineCommand as defineCommand3 } from "citty";
|
|
|
2122
2134
|
|
|
2123
2135
|
// src/lifecycle/cursor-update-awareness.ts
|
|
2124
2136
|
import { access as access2, readFile as readFile7 } from "fs/promises";
|
|
2137
|
+
import path11 from "path";
|
|
2138
|
+
|
|
2139
|
+
// src/lifecycle/context-config.ts
|
|
2125
2140
|
import path10 from "path";
|
|
2141
|
+
var CONTEXT_CONFIG_REL = path10.join(".cursor", "context", "config.json");
|
|
2142
|
+
function contextConfigPath(cwd) {
|
|
2143
|
+
return path10.join(cwd, CONTEXT_CONFIG_REL);
|
|
2144
|
+
}
|
|
2145
|
+
async function loadContextConfig(cwd) {
|
|
2146
|
+
return readJson(contextConfigPath(cwd));
|
|
2147
|
+
}
|
|
2148
|
+
function intervalElapsed(lastCheckedAt, intervalDays) {
|
|
2149
|
+
if (!lastCheckedAt) return true;
|
|
2150
|
+
const last = Date.parse(lastCheckedAt);
|
|
2151
|
+
if (Number.isNaN(last)) return true;
|
|
2152
|
+
return Date.now() - last >= intervalDays * 24 * 60 * 60 * 1e3;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// src/lifecycle/cursor-update-awareness.ts
|
|
2126
2156
|
var DEFAULT_CURSOR_CHANGELOG_URL = "https://cursor.com/changelog";
|
|
2127
2157
|
var CHANGELOG_FETCH_TIMEOUT_MS = 15e3;
|
|
2128
2158
|
var CURSOR_VERSION_MAJOR_MAX = 20;
|
|
2129
|
-
var INVENTORY_REL =
|
|
2130
|
-
var FEATURES_REL =
|
|
2159
|
+
var INVENTORY_REL = path11.join("docs", "cursor-native-audit.md");
|
|
2160
|
+
var FEATURES_REL = path11.join("docs", "cursor-3-features.md");
|
|
2131
2161
|
async function resolveInventoryRoot(cwd) {
|
|
2132
|
-
let dir =
|
|
2162
|
+
let dir = path11.resolve(cwd);
|
|
2133
2163
|
for (; ; ) {
|
|
2134
2164
|
try {
|
|
2135
|
-
await access2(
|
|
2165
|
+
await access2(path11.join(dir, INVENTORY_REL));
|
|
2136
2166
|
return dir;
|
|
2137
2167
|
} catch {
|
|
2138
2168
|
}
|
|
2139
|
-
if (await fileExists(
|
|
2140
|
-
const parent =
|
|
2169
|
+
if (await fileExists(path11.join(dir, ".git"))) break;
|
|
2170
|
+
const parent = path11.dirname(dir);
|
|
2141
2171
|
if (parent === dir) break;
|
|
2142
2172
|
dir = parent;
|
|
2143
2173
|
}
|
|
@@ -2173,18 +2203,7 @@ function readCursorUpdateCheckPrefs(config) {
|
|
|
2173
2203
|
changelogUrl
|
|
2174
2204
|
};
|
|
2175
2205
|
}
|
|
2176
|
-
function intervalElapsed(lastCheckedAt, intervalDays) {
|
|
2177
|
-
if (!lastCheckedAt) return true;
|
|
2178
|
-
const last = Date.parse(lastCheckedAt);
|
|
2179
|
-
if (Number.isNaN(last)) return true;
|
|
2180
|
-
return Date.now() - last >= intervalDays * 24 * 60 * 60 * 1e3;
|
|
2181
|
-
}
|
|
2182
|
-
async function loadContextConfig(cwd) {
|
|
2183
|
-
const configPath = path10.join(cwd, ".cursor", "context", "config.json");
|
|
2184
|
-
return readJson(configPath);
|
|
2185
|
-
}
|
|
2186
2206
|
async function stampCursorUpdateCheck(cwd, patch) {
|
|
2187
|
-
const configPath = path10.join(cwd, ".cursor", "context", "config.json");
|
|
2188
2207
|
const existing = await loadContextConfig(cwd) ?? {};
|
|
2189
2208
|
const prev = existing.cursorUpdateCheck && typeof existing.cursorUpdateCheck === "object" ? { ...existing.cursorUpdateCheck } : {};
|
|
2190
2209
|
const nextSeen = patch.lastSeenCursorVersion !== void 0 ? patch.lastSeenCursorVersion : void 0;
|
|
@@ -2194,7 +2213,7 @@ async function stampCursorUpdateCheck(cwd, patch) {
|
|
|
2194
2213
|
...prev,
|
|
2195
2214
|
lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2196
2215
|
};
|
|
2197
|
-
await writeJson(
|
|
2216
|
+
await writeJson(contextConfigPath(cwd), existing);
|
|
2198
2217
|
return;
|
|
2199
2218
|
}
|
|
2200
2219
|
existing.cursorUpdateCheck = {
|
|
@@ -2203,7 +2222,7 @@ async function stampCursorUpdateCheck(cwd, patch) {
|
|
|
2203
2222
|
lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2204
2223
|
...nextSeen !== void 0 ? { lastSeenCursorVersion: nextSeen } : {}
|
|
2205
2224
|
};
|
|
2206
|
-
await writeJson(
|
|
2225
|
+
await writeJson(contextConfigPath(cwd), existing);
|
|
2207
2226
|
}
|
|
2208
2227
|
function isPlausibleCursorVersion(version) {
|
|
2209
2228
|
const parts = String(version).split(".");
|
|
@@ -2453,8 +2472,8 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2453
2472
|
message: missingInventoryMessage()
|
|
2454
2473
|
});
|
|
2455
2474
|
}
|
|
2456
|
-
const inventoryPath =
|
|
2457
|
-
const featuresPath =
|
|
2475
|
+
const inventoryPath = path11.join(inventoryRoot, INVENTORY_REL);
|
|
2476
|
+
const featuresPath = path11.join(inventoryRoot, FEATURES_REL);
|
|
2458
2477
|
let inventoryMd;
|
|
2459
2478
|
try {
|
|
2460
2479
|
inventoryMd = await readFile7(inventoryPath, "utf8");
|
|
@@ -2667,14 +2686,14 @@ var cursorAwarenessCommand = defineCommand3({
|
|
|
2667
2686
|
// src/commands/dashboard-broadcast.ts
|
|
2668
2687
|
import { spawn as spawn3 } from "child_process";
|
|
2669
2688
|
import { access as access4 } from "fs/promises";
|
|
2670
|
-
import
|
|
2689
|
+
import path13 from "path";
|
|
2671
2690
|
import { defineCommand as defineCommand5 } from "citty";
|
|
2672
2691
|
|
|
2673
2692
|
// src/commands/dashboard.ts
|
|
2674
2693
|
import { execFileSync, spawn as spawn2 } from "child_process";
|
|
2675
2694
|
import { existsSync, readFileSync as readFileSync2, realpathSync } from "fs";
|
|
2676
2695
|
import { access as access3 } from "fs/promises";
|
|
2677
|
-
import
|
|
2696
|
+
import path12 from "path";
|
|
2678
2697
|
import { fileURLToPath } from "url";
|
|
2679
2698
|
import { defineCommand as defineCommand4 } from "citty";
|
|
2680
2699
|
|
|
@@ -2684,7 +2703,7 @@ import { networkInterfaces } from "os";
|
|
|
2684
2703
|
import { resolve } from "path";
|
|
2685
2704
|
var REPO_ROOT_ENV = "MISSION_CONTROL_REPO_ROOT";
|
|
2686
2705
|
var KIT_ROOT_ENV_KEYS = Object.freeze(["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]);
|
|
2687
|
-
var
|
|
2706
|
+
var CONTEXT_CONFIG_REL2 = ".cursor/context/config.json";
|
|
2688
2707
|
var CONFIG_PERSONA_IDS = Object.freeze(["autopilot", "night-shift", "ghost-runner"]);
|
|
2689
2708
|
var CONFIG_PERSONA_MODES = Object.freeze(["continue-plan", "run-plan", "cli-run-plan"]);
|
|
2690
2709
|
var CONFIG_REVIEW_BACKENDS = Object.freeze(["auto", "claude", "cursor"]);
|
|
@@ -2697,7 +2716,7 @@ function resolveContextConfigPath(repoRoot, fsHooks = {}) {
|
|
|
2697
2716
|
if (typeof repoRoot !== "string" || !repoRoot) {
|
|
2698
2717
|
return { ok: false, error: "invalid repo root" };
|
|
2699
2718
|
}
|
|
2700
|
-
const abs = resolve(repoRoot,
|
|
2719
|
+
const abs = resolve(repoRoot, CONTEXT_CONFIG_REL2);
|
|
2701
2720
|
const contextDir = resolve(repoRoot, ".cursor", "context");
|
|
2702
2721
|
const absNorm = abs.replace(/\\/g, "/");
|
|
2703
2722
|
if (!absNorm.endsWith("/.cursor/context/config.json")) {
|
|
@@ -2763,7 +2782,7 @@ function readPreferredBrowserFromConfig(configPath, fsHooks = {}) {
|
|
|
2763
2782
|
|
|
2764
2783
|
// src/commands/dashboard.ts
|
|
2765
2784
|
function readPreferredBrowserFromWorkspace(cwd, readFile31 = readFileSync2) {
|
|
2766
|
-
const resolved = resolveContextConfigPath(
|
|
2785
|
+
const resolved = resolveContextConfigPath(path12.resolve(cwd), {
|
|
2767
2786
|
existsSync,
|
|
2768
2787
|
realpathSync
|
|
2769
2788
|
});
|
|
@@ -2785,61 +2804,61 @@ function applyDashboardOpenEnv(env, opts) {
|
|
|
2785
2804
|
return next;
|
|
2786
2805
|
}
|
|
2787
2806
|
function dashboardSpawnEnv(env, snapshotRoot, opts) {
|
|
2788
|
-
const root =
|
|
2807
|
+
const root = path12.resolve(snapshotRoot);
|
|
2789
2808
|
return applyDashboardOpenEnv({ ...env, MISSION_CONTROL_REPO_ROOT: root }, { ...opts, cwd: root });
|
|
2790
2809
|
}
|
|
2791
2810
|
function dashboardProcessTitle(snapshotRoot) {
|
|
2792
|
-
const base =
|
|
2811
|
+
const base = path12.basename(path12.resolve(snapshotRoot)) || "workspace";
|
|
2793
2812
|
return `Mission Control \xB7 ${base}`;
|
|
2794
2813
|
}
|
|
2795
2814
|
function bundledDashboardCandidates(filename, moduleUrl = import.meta.url) {
|
|
2796
|
-
const here =
|
|
2815
|
+
const here = path12.dirname(fileURLToPath(moduleUrl));
|
|
2797
2816
|
return [
|
|
2798
2817
|
// Published / built: <pkg>/dist/index.js -> <pkg>/dashboard/
|
|
2799
|
-
|
|
2818
|
+
path12.join(here, "..", "dashboard", filename),
|
|
2800
2819
|
// Dev source: packages/cli/src/commands/*.ts -> packages/cli/dashboard/ (sync copy)
|
|
2801
|
-
|
|
2820
|
+
path12.join(here, "..", "..", "dashboard", filename),
|
|
2802
2821
|
// From src/commands: packages/dashboard/ (unused layout); from dist: two levels above package
|
|
2803
|
-
|
|
2822
|
+
path12.join(here, "..", "..", "..", "dashboard", filename)
|
|
2804
2823
|
];
|
|
2805
2824
|
}
|
|
2806
2825
|
async function firstExisting(candidates) {
|
|
2807
2826
|
for (const candidate2 of candidates) {
|
|
2808
2827
|
try {
|
|
2809
2828
|
await access3(candidate2);
|
|
2810
|
-
return
|
|
2829
|
+
return path12.resolve(candidate2);
|
|
2811
2830
|
} catch {
|
|
2812
2831
|
}
|
|
2813
2832
|
}
|
|
2814
2833
|
return null;
|
|
2815
2834
|
}
|
|
2816
2835
|
async function findDashboardStart(cwd, env = process.env, options = {}) {
|
|
2817
|
-
let dir =
|
|
2836
|
+
let dir = path12.resolve(cwd);
|
|
2818
2837
|
for (; ; ) {
|
|
2819
|
-
const candidate2 =
|
|
2838
|
+
const candidate2 = path12.join(dir, "dashboard", "start.mjs");
|
|
2820
2839
|
try {
|
|
2821
2840
|
await access3(candidate2);
|
|
2822
2841
|
return candidate2;
|
|
2823
2842
|
} catch {
|
|
2824
2843
|
}
|
|
2825
|
-
const parent =
|
|
2844
|
+
const parent = path12.dirname(dir);
|
|
2826
2845
|
if (parent === dir) break;
|
|
2827
2846
|
dir = parent;
|
|
2828
2847
|
}
|
|
2829
2848
|
for (const key of ["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]) {
|
|
2830
2849
|
const base = env[key];
|
|
2831
2850
|
if (!base || typeof base !== "string" || !base.trim()) continue;
|
|
2832
|
-
const candidate2 =
|
|
2851
|
+
const candidate2 = path12.join(path12.resolve(base.trim()), "dashboard", "start.mjs");
|
|
2833
2852
|
try {
|
|
2834
2853
|
await access3(candidate2);
|
|
2835
2854
|
return candidate2;
|
|
2836
2855
|
} catch {
|
|
2837
2856
|
}
|
|
2838
2857
|
}
|
|
2839
|
-
const sibling =
|
|
2858
|
+
const sibling = path12.join(path12.resolve(cwd), "..", "agent-kit", "dashboard", "start.mjs");
|
|
2840
2859
|
try {
|
|
2841
2860
|
await access3(sibling);
|
|
2842
|
-
return
|
|
2861
|
+
return path12.resolve(sibling);
|
|
2843
2862
|
} catch {
|
|
2844
2863
|
}
|
|
2845
2864
|
return firstExisting(
|
|
@@ -2847,13 +2866,13 @@ async function findDashboardStart(cwd, env = process.env, options = {}) {
|
|
|
2847
2866
|
);
|
|
2848
2867
|
}
|
|
2849
2868
|
function resolveDashboardSnapshotRoot(cwd) {
|
|
2850
|
-
const abs =
|
|
2869
|
+
const abs = path12.resolve(cwd);
|
|
2851
2870
|
let dir = abs;
|
|
2852
2871
|
for (; ; ) {
|
|
2853
|
-
if (existsSync(
|
|
2872
|
+
if (existsSync(path12.join(dir, ".cursor", "agent-kit.json"))) {
|
|
2854
2873
|
return dir;
|
|
2855
2874
|
}
|
|
2856
|
-
const parent =
|
|
2875
|
+
const parent = path12.dirname(dir);
|
|
2857
2876
|
if (parent === dir) break;
|
|
2858
2877
|
dir = parent;
|
|
2859
2878
|
}
|
|
@@ -2863,7 +2882,7 @@ function resolveDashboardSnapshotRoot(cwd) {
|
|
|
2863
2882
|
timeout: 5e3,
|
|
2864
2883
|
stdio: ["ignore", "pipe", "ignore"]
|
|
2865
2884
|
}).trim();
|
|
2866
|
-
if (top) return
|
|
2885
|
+
if (top) return path12.resolve(top);
|
|
2867
2886
|
} catch {
|
|
2868
2887
|
}
|
|
2869
2888
|
return abs;
|
|
@@ -2871,7 +2890,7 @@ function resolveDashboardSnapshotRoot(cwd) {
|
|
|
2871
2890
|
function runStartScript(startPath, env) {
|
|
2872
2891
|
return new Promise((resolve3, reject) => {
|
|
2873
2892
|
const child = spawn2(process.execPath, [startPath], {
|
|
2874
|
-
cwd:
|
|
2893
|
+
cwd: path12.dirname(path12.dirname(startPath)),
|
|
2875
2894
|
env,
|
|
2876
2895
|
stdio: "inherit"
|
|
2877
2896
|
});
|
|
@@ -2939,30 +2958,30 @@ var dashboardCommand = defineCommand4({
|
|
|
2939
2958
|
|
|
2940
2959
|
// src/commands/dashboard-broadcast.ts
|
|
2941
2960
|
async function findDashboardBroadcastStart(cwd, env = process.env, options = {}) {
|
|
2942
|
-
let dir =
|
|
2961
|
+
let dir = path13.resolve(cwd);
|
|
2943
2962
|
for (; ; ) {
|
|
2944
|
-
const candidate2 =
|
|
2963
|
+
const candidate2 = path13.join(dir, "dashboard", "start-broadcast.mjs");
|
|
2945
2964
|
try {
|
|
2946
2965
|
await access4(candidate2);
|
|
2947
2966
|
return candidate2;
|
|
2948
2967
|
} catch {
|
|
2949
2968
|
}
|
|
2950
|
-
const parent =
|
|
2969
|
+
const parent = path13.dirname(dir);
|
|
2951
2970
|
if (parent === dir) break;
|
|
2952
2971
|
dir = parent;
|
|
2953
2972
|
}
|
|
2954
2973
|
for (const key of ["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]) {
|
|
2955
2974
|
const base = env[key];
|
|
2956
2975
|
if (!base || typeof base !== "string" || !base.trim()) continue;
|
|
2957
|
-
const candidate2 =
|
|
2976
|
+
const candidate2 = path13.join(path13.resolve(base.trim()), "dashboard", "start-broadcast.mjs");
|
|
2958
2977
|
try {
|
|
2959
2978
|
await access4(candidate2);
|
|
2960
2979
|
return candidate2;
|
|
2961
2980
|
} catch {
|
|
2962
2981
|
}
|
|
2963
2982
|
}
|
|
2964
|
-
const sibling =
|
|
2965
|
-
|
|
2983
|
+
const sibling = path13.join(
|
|
2984
|
+
path13.resolve(cwd),
|
|
2966
2985
|
"..",
|
|
2967
2986
|
"agent-kit",
|
|
2968
2987
|
"dashboard",
|
|
@@ -2970,7 +2989,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
2970
2989
|
);
|
|
2971
2990
|
try {
|
|
2972
2991
|
await access4(sibling);
|
|
2973
|
-
return
|
|
2992
|
+
return path13.resolve(sibling);
|
|
2974
2993
|
} catch {
|
|
2975
2994
|
}
|
|
2976
2995
|
for (const candidate2 of bundledDashboardCandidates(
|
|
@@ -2979,7 +2998,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
2979
2998
|
)) {
|
|
2980
2999
|
try {
|
|
2981
3000
|
await access4(candidate2);
|
|
2982
|
-
return
|
|
3001
|
+
return path13.resolve(candidate2);
|
|
2983
3002
|
} catch {
|
|
2984
3003
|
}
|
|
2985
3004
|
}
|
|
@@ -2988,7 +3007,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
2988
3007
|
function runStartScript2(startPath, env) {
|
|
2989
3008
|
return new Promise((resolve3, reject) => {
|
|
2990
3009
|
const child = spawn3(process.execPath, [startPath], {
|
|
2991
|
-
cwd:
|
|
3010
|
+
cwd: path13.dirname(path13.dirname(startPath)),
|
|
2992
3011
|
env,
|
|
2993
3012
|
stdio: "inherit"
|
|
2994
3013
|
});
|
|
@@ -3043,7 +3062,7 @@ import { defineCommand as defineCommand6 } from "citty";
|
|
|
3043
3062
|
|
|
3044
3063
|
// src/lifecycle/diff.ts
|
|
3045
3064
|
import { readFile as readFile8 } from "fs/promises";
|
|
3046
|
-
import
|
|
3065
|
+
import path14 from "path";
|
|
3047
3066
|
async function readIfExists(abs) {
|
|
3048
3067
|
try {
|
|
3049
3068
|
return await readFile8(abs, "utf8");
|
|
@@ -3052,7 +3071,7 @@ async function readIfExists(abs) {
|
|
|
3052
3071
|
}
|
|
3053
3072
|
}
|
|
3054
3073
|
async function comparePair(registryRoot, projectRoot, sourceRel, targetRel, protectedGlobs) {
|
|
3055
|
-
const posixTarget = targetRel.split(
|
|
3074
|
+
const posixTarget = targetRel.split(path14.sep).join("/");
|
|
3056
3075
|
if (isProtectedPath(posixTarget, protectedGlobs)) {
|
|
3057
3076
|
return { path: posixTarget, status: "protected" };
|
|
3058
3077
|
}
|
|
@@ -3082,7 +3101,7 @@ async function diffAgainstRegistry(registryRoot, projectRoot, manifest) {
|
|
|
3082
3101
|
const entries = [];
|
|
3083
3102
|
const seen = /* @__PURE__ */ new Set();
|
|
3084
3103
|
const pushUnique = async (sourceRel, targetRel) => {
|
|
3085
|
-
const key = targetRel.split(
|
|
3104
|
+
const key = targetRel.split(path14.sep).join("/");
|
|
3086
3105
|
if (seen.has(key)) return;
|
|
3087
3106
|
seen.add(key);
|
|
3088
3107
|
entries.push(
|
|
@@ -3187,14 +3206,116 @@ var diffCommand = defineCommand6({
|
|
|
3187
3206
|
});
|
|
3188
3207
|
|
|
3189
3208
|
// src/commands/doctor.ts
|
|
3190
|
-
import
|
|
3209
|
+
import path28 from "path";
|
|
3191
3210
|
import { defineCommand as defineCommand7 } from "citty";
|
|
3192
3211
|
|
|
3193
3212
|
// src/invariants/hooks-health.ts
|
|
3194
3213
|
import { execFile as execFile2 } from "child_process";
|
|
3195
|
-
import { constants as constants2, access as access5, readFile as
|
|
3196
|
-
import
|
|
3214
|
+
import { constants as constants2, access as access5, readFile as readFile10, readdir as readdir4, stat as stat2 } from "fs/promises";
|
|
3215
|
+
import path16 from "path";
|
|
3197
3216
|
import { promisify as promisify2 } from "util";
|
|
3217
|
+
|
|
3218
|
+
// src/generator/claude-session-start-hook.ts
|
|
3219
|
+
import { readFile as readFile9, writeFile as writeFile5 } from "fs/promises";
|
|
3220
|
+
import path15 from "path";
|
|
3221
|
+
var CLAUDE_SETTINGS_REL = ".claude/settings.json";
|
|
3222
|
+
var RESOLVE_AGENT_KIT_REL = ".cursor/hooks/agent/resolve-agent-kit.sh";
|
|
3223
|
+
var SESSION_START_HOOK_MARKER = "hook session-start --format claude";
|
|
3224
|
+
var SESSION_START_DEGRADED_TEXT = "Agent Kit hooks are running in degraded fail-open mode: the agent-kit CLI could not be resolved (checked AGENT_KIT_HOOK_BIN, PATH, node_modules/.bin/agent-kit). Slash command adapters still work; session-context injection is inactive. Fix: install the CLI (npm i -D @dadado/agent-kit-cli) or set AGENT_KIT_HOOK_BIN.";
|
|
3225
|
+
function buildSessionStartHookCommand() {
|
|
3226
|
+
return `. "\${CLAUDE_PROJECT_DIR}/${RESOLVE_AGENT_KIT_REL}" 2>/dev/null && resolve_agent_kit && exec $AGENT_KIT_RESOLVED hook session-start --format claude; printf '%s' ${shellSingleQuote(SESSION_START_DEGRADED_TEXT)}`;
|
|
3227
|
+
}
|
|
3228
|
+
function shellSingleQuote(text) {
|
|
3229
|
+
return `'${text.replace(/'/g, "'\\''")}'`;
|
|
3230
|
+
}
|
|
3231
|
+
function buildSessionStartHookEntry() {
|
|
3232
|
+
return {
|
|
3233
|
+
type: "command",
|
|
3234
|
+
command: buildSessionStartHookCommand(),
|
|
3235
|
+
timeout: 15,
|
|
3236
|
+
statusMessage: "Loading Agent Kit session context"
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
function isPlainObject2(value) {
|
|
3240
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3241
|
+
}
|
|
3242
|
+
function groupHasMarker(group) {
|
|
3243
|
+
if (!isPlainObject2(group) || !Array.isArray(group.hooks)) return false;
|
|
3244
|
+
return group.hooks.some(
|
|
3245
|
+
(h) => isPlainObject2(h) && typeof h.command === "string" && h.command.includes(SESSION_START_HOOK_MARKER)
|
|
3246
|
+
);
|
|
3247
|
+
}
|
|
3248
|
+
function mergeSessionStartHookIntoSettings(existingRaw) {
|
|
3249
|
+
const entry = buildSessionStartHookEntry();
|
|
3250
|
+
const newGroup = { hooks: [entry] };
|
|
3251
|
+
let root = {};
|
|
3252
|
+
if (existingRaw !== null && existingRaw.trim() !== "") {
|
|
3253
|
+
try {
|
|
3254
|
+
const parsed = JSON.parse(existingRaw);
|
|
3255
|
+
if (!isPlainObject2(parsed)) throw new Error("root is not an object");
|
|
3256
|
+
root = parsed;
|
|
3257
|
+
} catch {
|
|
3258
|
+
return {
|
|
3259
|
+
content: null,
|
|
3260
|
+
status: "unavailable",
|
|
3261
|
+
instructions: instructionsBlock(entry)
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
const hooks = isPlainObject2(root.hooks) ? { ...root.hooks } : {};
|
|
3266
|
+
const sessionStart = Array.isArray(hooks.SessionStart) ? [...hooks.SessionStart] : [];
|
|
3267
|
+
const existingIndex = sessionStart.findIndex((g) => groupHasMarker(g));
|
|
3268
|
+
let status;
|
|
3269
|
+
if (existingIndex === -1) {
|
|
3270
|
+
sessionStart.push(newGroup);
|
|
3271
|
+
status = "applied";
|
|
3272
|
+
} else {
|
|
3273
|
+
const current = sessionStart[existingIndex];
|
|
3274
|
+
if (JSON.stringify(current) === JSON.stringify(newGroup)) {
|
|
3275
|
+
status = "unchanged";
|
|
3276
|
+
} else {
|
|
3277
|
+
sessionStart[existingIndex] = newGroup;
|
|
3278
|
+
status = "refreshed";
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
hooks.SessionStart = sessionStart;
|
|
3282
|
+
root.hooks = hooks;
|
|
3283
|
+
return { content: `${JSON.stringify(root, null, 2)}
|
|
3284
|
+
`, status };
|
|
3285
|
+
}
|
|
3286
|
+
function instructionsBlock(entry) {
|
|
3287
|
+
return [
|
|
3288
|
+
`Could not parse the existing ${CLAUDE_SETTINGS_REL} as JSON, so Agent Kit did not touch it.`,
|
|
3289
|
+
"Add this hook by hand under hooks.SessionStart (create the arrays if they do not exist):",
|
|
3290
|
+
"",
|
|
3291
|
+
JSON.stringify(entry, null, 2)
|
|
3292
|
+
].join("\n");
|
|
3293
|
+
}
|
|
3294
|
+
async function writeClaudeSessionStartHook(rootDir) {
|
|
3295
|
+
const abs = path15.join(rootDir, CLAUDE_SETTINGS_REL);
|
|
3296
|
+
let existing = null;
|
|
3297
|
+
try {
|
|
3298
|
+
existing = await readFile9(abs, "utf8");
|
|
3299
|
+
} catch {
|
|
3300
|
+
existing = null;
|
|
3301
|
+
}
|
|
3302
|
+
const merged = mergeSessionStartHookIntoSettings(existing);
|
|
3303
|
+
if (merged.status === "unavailable" || merged.content === null) {
|
|
3304
|
+
return {
|
|
3305
|
+
relativePath: CLAUDE_SETTINGS_REL,
|
|
3306
|
+
status: "unavailable",
|
|
3307
|
+
instructions: merged.instructions
|
|
3308
|
+
};
|
|
3309
|
+
}
|
|
3310
|
+
if (merged.status === "unchanged") {
|
|
3311
|
+
return { relativePath: CLAUDE_SETTINGS_REL, status: "unchanged" };
|
|
3312
|
+
}
|
|
3313
|
+
await ensureDir(path15.dirname(abs));
|
|
3314
|
+
await writeFile5(abs, merged.content, "utf8");
|
|
3315
|
+
return { relativePath: CLAUDE_SETTINGS_REL, status: merged.status };
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
// src/invariants/hooks-health.ts
|
|
3198
3319
|
var execFileAsync2 = promisify2(execFile2);
|
|
3199
3320
|
var GIT_HOOK_CANONICAL_NAMES = ["pre-commit", "pre-push", "prepare-commit-msg"];
|
|
3200
3321
|
var EXPECTED_EVENTS = [
|
|
@@ -3221,10 +3342,10 @@ async function isExecutable(p) {
|
|
|
3221
3342
|
}
|
|
3222
3343
|
}
|
|
3223
3344
|
async function resolveAgentKitCli(rootDir) {
|
|
3224
|
-
const root =
|
|
3345
|
+
const root = path16.resolve(rootDir);
|
|
3225
3346
|
const candidates = [
|
|
3226
|
-
|
|
3227
|
-
|
|
3347
|
+
path16.join(root, "node_modules", ".bin", "agent-kit"),
|
|
3348
|
+
path16.join(root, "packages", "cli", "dist", "index.js")
|
|
3228
3349
|
];
|
|
3229
3350
|
for (const c of candidates) {
|
|
3230
3351
|
if (await exists(c)) return c;
|
|
@@ -3244,8 +3365,8 @@ function commandLooksLikeAdapter(command) {
|
|
|
3244
3365
|
return m?.[1] ?? null;
|
|
3245
3366
|
}
|
|
3246
3367
|
async function assessGitHooksInstallDrift(rootDir) {
|
|
3247
|
-
const root =
|
|
3248
|
-
const canonicalDir =
|
|
3368
|
+
const root = path16.resolve(rootDir);
|
|
3369
|
+
const canonicalDir = path16.join(root, "git-hooks");
|
|
3249
3370
|
if (!await exists(canonicalDir)) return [];
|
|
3250
3371
|
let hooksPathCfg = "";
|
|
3251
3372
|
try {
|
|
@@ -3258,12 +3379,12 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
3258
3379
|
} catch {
|
|
3259
3380
|
}
|
|
3260
3381
|
if (hooksPathCfg) {
|
|
3261
|
-
const resolved =
|
|
3262
|
-
if (resolved ===
|
|
3382
|
+
const resolved = path16.isAbsolute(hooksPathCfg) ? path16.normalize(hooksPathCfg) : path16.normalize(path16.join(root, hooksPathCfg));
|
|
3383
|
+
if (resolved === path16.normalize(canonicalDir)) {
|
|
3263
3384
|
return [];
|
|
3264
3385
|
}
|
|
3265
3386
|
}
|
|
3266
|
-
const gitDir =
|
|
3387
|
+
const gitDir = path16.join(root, ".git");
|
|
3267
3388
|
if (!await exists(gitDir)) return [];
|
|
3268
3389
|
try {
|
|
3269
3390
|
const st = await stat2(gitDir);
|
|
@@ -3271,7 +3392,7 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
3271
3392
|
} catch {
|
|
3272
3393
|
return [];
|
|
3273
3394
|
}
|
|
3274
|
-
const installedDir =
|
|
3395
|
+
const installedDir = path16.join(gitDir, "hooks");
|
|
3275
3396
|
const advisories = [];
|
|
3276
3397
|
const installHint = "Install or refresh with: `cp git-hooks/<name> .git/hooks/<name> && chmod +x .git/hooks/<name>` (see git-hooks/README.md)";
|
|
3277
3398
|
let names = [...GIT_HOOK_CANONICAL_NAMES];
|
|
@@ -3284,8 +3405,8 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
3284
3405
|
} catch {
|
|
3285
3406
|
}
|
|
3286
3407
|
for (const name of names) {
|
|
3287
|
-
const canonical =
|
|
3288
|
-
const installed =
|
|
3408
|
+
const canonical = path16.join(canonicalDir, name);
|
|
3409
|
+
const installed = path16.join(installedDir, name);
|
|
3289
3410
|
if (!await exists(canonical)) continue;
|
|
3290
3411
|
if (!await exists(installed)) {
|
|
3291
3412
|
advisories.push(
|
|
@@ -3294,7 +3415,7 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
3294
3415
|
continue;
|
|
3295
3416
|
}
|
|
3296
3417
|
try {
|
|
3297
|
-
const [a, b] = await Promise.all([
|
|
3418
|
+
const [a, b] = await Promise.all([readFile10(canonical, "utf8"), readFile10(installed, "utf8")]);
|
|
3298
3419
|
if (a !== b) {
|
|
3299
3420
|
advisories.push(
|
|
3300
3421
|
`git-hooks drift: \`.git/hooks/${name}\` differs from \`git-hooks/${name}\`. ${installHint}`
|
|
@@ -3309,9 +3430,9 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
3309
3430
|
return advisories;
|
|
3310
3431
|
}
|
|
3311
3432
|
async function assessHooksHealth(rootDir) {
|
|
3312
|
-
const root =
|
|
3433
|
+
const root = path16.resolve(rootDir);
|
|
3313
3434
|
const hooksJsonPath = ".cursor/hooks.json";
|
|
3314
|
-
const hooksJsonAbs =
|
|
3435
|
+
const hooksJsonAbs = path16.join(root, hooksJsonPath);
|
|
3315
3436
|
const reasons = [];
|
|
3316
3437
|
const wiredEvents = [];
|
|
3317
3438
|
const advisories = await assessGitHooksInstallDrift(root);
|
|
@@ -3327,7 +3448,7 @@ async function assessHooksHealth(rootDir) {
|
|
|
3327
3448
|
}
|
|
3328
3449
|
let parsed;
|
|
3329
3450
|
try {
|
|
3330
|
-
parsed = JSON.parse(await
|
|
3451
|
+
parsed = JSON.parse(await readFile10(hooksJsonAbs, "utf8"));
|
|
3331
3452
|
} catch {
|
|
3332
3453
|
return {
|
|
3333
3454
|
status: "degraded",
|
|
@@ -3357,14 +3478,14 @@ async function assessHooksHealth(rootDir) {
|
|
|
3357
3478
|
reasons.push(`missing hook event: ${event}`);
|
|
3358
3479
|
}
|
|
3359
3480
|
}
|
|
3360
|
-
const resolveLib =
|
|
3481
|
+
const resolveLib = path16.join(root, RESOLVE_AGENT_KIT_REL);
|
|
3361
3482
|
if (!await exists(resolveLib)) {
|
|
3362
|
-
reasons.push(
|
|
3483
|
+
reasons.push(`missing \`${RESOLVE_AGENT_KIT_REL}\` (thin adapter resolver)`);
|
|
3363
3484
|
} else if (!await isExecutable(resolveLib)) {
|
|
3364
|
-
reasons.push(
|
|
3485
|
+
reasons.push(`\`${RESOLVE_AGENT_KIT_REL}\` is not executable (chmod +x)`);
|
|
3365
3486
|
}
|
|
3366
3487
|
for (const rel of adapterRels) {
|
|
3367
|
-
const abs =
|
|
3488
|
+
const abs = path16.join(root, rel);
|
|
3368
3489
|
if (!await exists(abs)) {
|
|
3369
3490
|
reasons.push(`missing adapter script: \`${rel}\``);
|
|
3370
3491
|
continue;
|
|
@@ -3405,7 +3526,7 @@ async function assessHooksHealth(rootDir) {
|
|
|
3405
3526
|
|
|
3406
3527
|
// src/lifecycle/check-updates.ts
|
|
3407
3528
|
import { execFile as execFile3 } from "child_process";
|
|
3408
|
-
import
|
|
3529
|
+
import path17 from "path";
|
|
3409
3530
|
import { promisify as promisify3 } from "util";
|
|
3410
3531
|
var execFileAsync3 = promisify3(execFile3);
|
|
3411
3532
|
var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/i;
|
|
@@ -3505,31 +3626,19 @@ function readUpdateCheckPrefs(config) {
|
|
|
3505
3626
|
lastCheckedAt: typeof uc.lastCheckedAt === "string" ? uc.lastCheckedAt : null
|
|
3506
3627
|
};
|
|
3507
3628
|
}
|
|
3508
|
-
function intervalElapsed2(lastCheckedAt, intervalDays) {
|
|
3509
|
-
if (!lastCheckedAt) return true;
|
|
3510
|
-
const last = Date.parse(lastCheckedAt);
|
|
3511
|
-
if (Number.isNaN(last)) return true;
|
|
3512
|
-
const ms = intervalDays * 24 * 60 * 60 * 1e3;
|
|
3513
|
-
return Date.now() - last >= ms;
|
|
3514
|
-
}
|
|
3515
|
-
async function loadContextConfig2(cwd) {
|
|
3516
|
-
const configPath = path15.join(cwd, ".cursor", "context", "config.json");
|
|
3517
|
-
return readJson(configPath);
|
|
3518
|
-
}
|
|
3519
3629
|
async function stampLastCheckedAt(cwd) {
|
|
3520
|
-
const
|
|
3521
|
-
const existing = await loadContextConfig2(cwd) ?? {};
|
|
3630
|
+
const existing = await loadContextConfig(cwd) ?? {};
|
|
3522
3631
|
const prev = existing.updateCheck && typeof existing.updateCheck === "object" ? { ...existing.updateCheck } : {};
|
|
3523
3632
|
existing.updateCheck = {
|
|
3524
3633
|
...DEFAULT_UPDATE_CHECK,
|
|
3525
3634
|
...prev,
|
|
3526
3635
|
lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3527
3636
|
};
|
|
3528
|
-
await writeJson(
|
|
3637
|
+
await writeJson(contextConfigPath(cwd), existing);
|
|
3529
3638
|
}
|
|
3530
3639
|
async function readLocalKitVersion(registryRoot) {
|
|
3531
3640
|
for (const rel of ["packages/cli/package.json", "package.json"]) {
|
|
3532
|
-
const data = await readJson(
|
|
3641
|
+
const data = await readJson(path17.join(registryRoot, rel));
|
|
3533
3642
|
if (data && typeof data.version === "string" && data.version.length > 0) {
|
|
3534
3643
|
return data.version;
|
|
3535
3644
|
}
|
|
@@ -3550,7 +3659,7 @@ async function checkAgainstLocalRegistry(cwd, manifest, options) {
|
|
|
3550
3659
|
status: "error",
|
|
3551
3660
|
installedVersion: manifest.version,
|
|
3552
3661
|
latestVersion: null,
|
|
3553
|
-
registryUrl:
|
|
3662
|
+
registryUrl: path17.resolve(registryPath),
|
|
3554
3663
|
registryRef: "local",
|
|
3555
3664
|
applyRecommended: false,
|
|
3556
3665
|
message: `Failed to resolve --registry: ${msg}`
|
|
@@ -3668,7 +3777,7 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
3668
3777
|
};
|
|
3669
3778
|
}
|
|
3670
3779
|
if (options.respectPrefs) {
|
|
3671
|
-
const config = await
|
|
3780
|
+
const config = await loadContextConfig(cwd);
|
|
3672
3781
|
const prefs = readUpdateCheckPrefs(config);
|
|
3673
3782
|
if (!prefs.enabled) {
|
|
3674
3783
|
return {
|
|
@@ -3681,7 +3790,7 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
3681
3790
|
message: "updateCheck.enabled is false (opt-in). Set true in .cursor/context/config.json to nudge."
|
|
3682
3791
|
};
|
|
3683
3792
|
}
|
|
3684
|
-
if (!
|
|
3793
|
+
if (!intervalElapsed(prefs.lastCheckedAt, prefs.intervalDays)) {
|
|
3685
3794
|
return {
|
|
3686
3795
|
status: "skipped-interval",
|
|
3687
3796
|
installedVersion: manifest.version,
|
|
@@ -3778,7 +3887,7 @@ function runningCliBehindMessage(running, latest) {
|
|
|
3778
3887
|
async function checkRunningCliVsNpmLatest(cwd, options = {}) {
|
|
3779
3888
|
const running = normalizeSemver(KIT_VERSION) ?? KIT_VERSION;
|
|
3780
3889
|
if (options.respectPrefs) {
|
|
3781
|
-
const config = await
|
|
3890
|
+
const config = await loadContextConfig(cwd);
|
|
3782
3891
|
const prefs = readUpdateCheckPrefs(config);
|
|
3783
3892
|
if (!prefs.enabled) {
|
|
3784
3893
|
return {
|
|
@@ -3788,7 +3897,7 @@ async function checkRunningCliVsNpmLatest(cwd, options = {}) {
|
|
|
3788
3897
|
message: "updateCheck.enabled is false (opt-in). Set true in .cursor/context/config.json to nudge."
|
|
3789
3898
|
};
|
|
3790
3899
|
}
|
|
3791
|
-
if (!
|
|
3900
|
+
if (!intervalElapsed(prefs.lastCheckedAt, prefs.intervalDays)) {
|
|
3792
3901
|
return {
|
|
3793
3902
|
status: "skipped-interval",
|
|
3794
3903
|
runningVersion: running,
|
|
@@ -3887,11 +3996,11 @@ async function warnIfRunningCliBehindNpm(cwd, options = {}) {
|
|
|
3887
3996
|
}
|
|
3888
3997
|
|
|
3889
3998
|
// src/readiness/env-checks.ts
|
|
3890
|
-
import { constants as constants3, access as access6, readFile as
|
|
3999
|
+
import { constants as constants3, access as access6, readFile as readFile11, stat as stat3 } from "fs/promises";
|
|
3891
4000
|
import { homedir as homedir2 } from "os";
|
|
3892
|
-
import
|
|
4001
|
+
import path18 from "path";
|
|
3893
4002
|
var MIN_NODE_MAJOR = 20;
|
|
3894
|
-
var CLI_PACKAGE_JSON_REL =
|
|
4003
|
+
var CLI_PACKAGE_JSON_REL = path18.join(
|
|
3895
4004
|
"lib",
|
|
3896
4005
|
"node_modules",
|
|
3897
4006
|
"@dadado",
|
|
@@ -3912,7 +4021,7 @@ function expandBinDirVars(raw, binDir) {
|
|
|
3912
4021
|
return raw.replace(/\$\{basedir\}/g, binDir).replace(/\$basedir/g, binDir);
|
|
3913
4022
|
}
|
|
3914
4023
|
function packageJsonFromDistIndex(distIndex) {
|
|
3915
|
-
return
|
|
4024
|
+
return path18.join(path18.dirname(distIndex), "..", "package.json");
|
|
3916
4025
|
}
|
|
3917
4026
|
function parsePackageVersion(contents) {
|
|
3918
4027
|
try {
|
|
@@ -3923,23 +4032,23 @@ function parsePackageVersion(contents) {
|
|
|
3923
4032
|
}
|
|
3924
4033
|
}
|
|
3925
4034
|
async function readBinVersion(binPath, options = {}) {
|
|
3926
|
-
const readFileImpl = options.readFileImpl ?? ((filePath) =>
|
|
3927
|
-
const binDir =
|
|
4035
|
+
const readFileImpl = options.readFileImpl ?? ((filePath) => readFile11(filePath, "utf8"));
|
|
4036
|
+
const binDir = path18.dirname(binPath);
|
|
3928
4037
|
const candidates = [
|
|
3929
|
-
|
|
3930
|
-
|
|
4038
|
+
path18.join(binDir, "..", CLI_PACKAGE_JSON_REL),
|
|
4039
|
+
path18.join(binDir, "..", "node_modules", "@dadado", "agent-kit-cli", "package.json")
|
|
3931
4040
|
];
|
|
3932
4041
|
try {
|
|
3933
4042
|
const contents = await readFileImpl(binPath);
|
|
3934
4043
|
for (const raw of extractDistIndexFromBinScript(contents)) {
|
|
3935
|
-
const distIndex =
|
|
4044
|
+
const distIndex = path18.resolve(binDir, expandBinDirVars(raw, binDir));
|
|
3936
4045
|
candidates.push(packageJsonFromDistIndex(distIndex));
|
|
3937
4046
|
}
|
|
3938
4047
|
} catch {
|
|
3939
4048
|
}
|
|
3940
4049
|
const seen = /* @__PURE__ */ new Set();
|
|
3941
4050
|
for (const candidate2 of candidates) {
|
|
3942
|
-
const resolved =
|
|
4051
|
+
const resolved = path18.resolve(candidate2);
|
|
3943
4052
|
if (seen.has(resolved)) continue;
|
|
3944
4053
|
seen.add(resolved);
|
|
3945
4054
|
try {
|
|
@@ -3953,11 +4062,11 @@ async function readBinVersion(binPath, options = {}) {
|
|
|
3953
4062
|
async function resolveBinOnPath(binName, env, platform) {
|
|
3954
4063
|
const pathVar = env.PATH ?? env.Path ?? "";
|
|
3955
4064
|
if (!pathVar) return null;
|
|
3956
|
-
const dirs = pathVar.split(
|
|
4065
|
+
const dirs = pathVar.split(path18.delimiter).filter(Boolean);
|
|
3957
4066
|
const candidates = platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.exe`, `${binName}.bat`] : [binName];
|
|
3958
4067
|
for (const dir of dirs) {
|
|
3959
4068
|
for (const candidate2 of candidates) {
|
|
3960
|
-
const abs =
|
|
4069
|
+
const abs = path18.join(dir, candidate2);
|
|
3961
4070
|
try {
|
|
3962
4071
|
await access6(abs, platform === "win32" ? void 0 : constants3.X_OK);
|
|
3963
4072
|
return abs;
|
|
@@ -3981,13 +4090,13 @@ function detectShellName(env, platform) {
|
|
|
3981
4090
|
}
|
|
3982
4091
|
const shellPath = env.SHELL;
|
|
3983
4092
|
if (!shellPath) return null;
|
|
3984
|
-
const base =
|
|
4093
|
+
const base = path18.basename(shellPath).trim();
|
|
3985
4094
|
return base || null;
|
|
3986
4095
|
}
|
|
3987
4096
|
function detectShellProfile(env, platform, homeDir) {
|
|
3988
4097
|
const shellName = detectShellName(env, platform);
|
|
3989
|
-
if (shellName === "zsh") return
|
|
3990
|
-
if (shellName === "bash") return
|
|
4098
|
+
if (shellName === "zsh") return path18.join(homeDir, ".zshrc");
|
|
4099
|
+
if (shellName === "bash") return path18.join(homeDir, ".bashrc");
|
|
3991
4100
|
return null;
|
|
3992
4101
|
}
|
|
3993
4102
|
function parseNpmrcPrefix(content, homeDir) {
|
|
@@ -3996,12 +4105,12 @@ function parseNpmrcPrefix(content, homeDir) {
|
|
|
3996
4105
|
if (!captured) return null;
|
|
3997
4106
|
let value = captured.trim().replace(/^["']|["']$/g, "");
|
|
3998
4107
|
if (value.startsWith("~")) {
|
|
3999
|
-
value =
|
|
4108
|
+
value = path18.join(homeDir, value.slice(1));
|
|
4000
4109
|
}
|
|
4001
4110
|
return value || null;
|
|
4002
4111
|
}
|
|
4003
4112
|
function heuristicPrefixFromExecPath(execPath, platform) {
|
|
4004
|
-
const p = platform === "win32" ?
|
|
4113
|
+
const p = platform === "win32" ? path18.win32 : path18.posix;
|
|
4005
4114
|
return platform === "win32" ? p.dirname(execPath) : p.dirname(p.dirname(execPath));
|
|
4006
4115
|
}
|
|
4007
4116
|
async function detectNpmPrefix(options = {}) {
|
|
@@ -4009,12 +4118,12 @@ async function detectNpmPrefix(options = {}) {
|
|
|
4009
4118
|
const platform = options.platform ?? process.platform;
|
|
4010
4119
|
const homeDir = options.homeDir ?? homedir2();
|
|
4011
4120
|
const execPath = options.execPath ?? process.execPath;
|
|
4012
|
-
const readFileImpl = options.readFileImpl ?? ((filePath) =>
|
|
4121
|
+
const readFileImpl = options.readFileImpl ?? ((filePath) => readFile11(filePath, "utf8"));
|
|
4013
4122
|
const envPrefix = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
|
|
4014
4123
|
if (envPrefix?.trim()) {
|
|
4015
4124
|
return { prefix: envPrefix.trim(), source: "env" };
|
|
4016
4125
|
}
|
|
4017
|
-
const userconfigPath = env.NPM_CONFIG_USERCONFIG ??
|
|
4126
|
+
const userconfigPath = env.NPM_CONFIG_USERCONFIG ?? path18.join(homeDir, ".npmrc");
|
|
4018
4127
|
try {
|
|
4019
4128
|
const content = await readFileImpl(userconfigPath);
|
|
4020
4129
|
const npmrcPrefix = parseNpmrcPrefix(content, homeDir);
|
|
@@ -4341,12 +4450,17 @@ function createReadinessReport(scan, options) {
|
|
|
4341
4450
|
}
|
|
4342
4451
|
|
|
4343
4452
|
// src/scanner/safe-fixes.ts
|
|
4344
|
-
import { readFile as
|
|
4345
|
-
import
|
|
4453
|
+
import { readFile as readFile16, writeFile as writeFile6 } from "fs/promises";
|
|
4454
|
+
import path26 from "path";
|
|
4455
|
+
|
|
4456
|
+
// src/scanner/detect-repository.ts
|
|
4457
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
4458
|
+
import path19 from "path";
|
|
4459
|
+
|
|
4460
|
+
// src/scanner/paths.ts
|
|
4461
|
+
var REPOSITORY_PROFILE_REL = ".cursor/agent-kit.config.json";
|
|
4346
4462
|
|
|
4347
4463
|
// src/scanner/detect-repository.ts
|
|
4348
|
-
import { readFile as readFile11 } from "fs/promises";
|
|
4349
|
-
import path17 from "path";
|
|
4350
4464
|
var CONTEXT_PATHS = [
|
|
4351
4465
|
["README.md", "README"],
|
|
4352
4466
|
["README", "README"],
|
|
@@ -4365,7 +4479,7 @@ var CONTEXT_PATHS = [
|
|
|
4365
4479
|
async function existingEvidence(rootDir, candidates) {
|
|
4366
4480
|
const evidence = await Promise.all(
|
|
4367
4481
|
candidates.map(
|
|
4368
|
-
async ([relativePath, label]) => await fileExists(
|
|
4482
|
+
async ([relativePath, label]) => await fileExists(path19.join(rootDir, relativePath)) ? { source: "file", value: `${relativePath}:${label}` } : void 0
|
|
4369
4483
|
)
|
|
4370
4484
|
);
|
|
4371
4485
|
return evidence.flatMap((item) => item ? [item] : []);
|
|
@@ -4399,7 +4513,7 @@ function isRepositoryPurpose(value) {
|
|
|
4399
4513
|
}
|
|
4400
4514
|
async function readConfirmedPurpose(rootDir) {
|
|
4401
4515
|
const configuration = await readJson(
|
|
4402
|
-
|
|
4516
|
+
path19.join(rootDir, REPOSITORY_PROFILE_REL)
|
|
4403
4517
|
);
|
|
4404
4518
|
const configuredPurpose = configuration?.purpose;
|
|
4405
4519
|
if (!configuredPurpose || !isRepositoryPurpose(configuredPurpose.value) || configuredPurpose.value === "unknown") {
|
|
@@ -4411,7 +4525,7 @@ async function readConfirmedPurpose(rootDir) {
|
|
|
4411
4525
|
value: configuredPurpose.value,
|
|
4412
4526
|
categories: categories.length > 0 ? categories : [configuredPurpose.value],
|
|
4413
4527
|
confidence: "high",
|
|
4414
|
-
evidence: evidence.length > 0 ? evidence : [{ source: "configuration", value:
|
|
4528
|
+
evidence: evidence.length > 0 ? evidence : [{ source: "configuration", value: `${REPOSITORY_PROFILE_REL}#purpose.value` }]
|
|
4415
4529
|
};
|
|
4416
4530
|
}
|
|
4417
4531
|
async function detectPurpose(rootDir, stack) {
|
|
@@ -4419,7 +4533,7 @@ async function detectPurpose(rootDir, stack) {
|
|
|
4419
4533
|
if (confirmed) return confirmed;
|
|
4420
4534
|
const entries = await listDirectory(rootDir);
|
|
4421
4535
|
const lowerEntries = entries.map((entry) => entry.toLowerCase());
|
|
4422
|
-
const packageJson = await readJson(
|
|
4536
|
+
const packageJson = await readJson(path19.join(rootDir, "package.json"));
|
|
4423
4537
|
const categories = [];
|
|
4424
4538
|
const evidence = [];
|
|
4425
4539
|
const add = (category, value2) => {
|
|
@@ -4459,16 +4573,16 @@ async function detectPurpose(rootDir, stack) {
|
|
|
4459
4573
|
}
|
|
4460
4574
|
async function detectAgentKit(rootDir) {
|
|
4461
4575
|
const manifestRelativePath = ".cursor/agent-kit.json";
|
|
4462
|
-
const manifestPath =
|
|
4576
|
+
const manifestPath = path19.join(rootDir, manifestRelativePath);
|
|
4463
4577
|
const installed = await fileExists(manifestPath);
|
|
4464
4578
|
const manifest = installed ? await readJson(manifestPath) : null;
|
|
4465
4579
|
return {
|
|
4466
4580
|
installed,
|
|
4467
4581
|
manifestPath: installed ? manifestRelativePath : void 0,
|
|
4468
4582
|
version: manifest?.version,
|
|
4469
|
-
hasPlans: await fileExists(
|
|
4470
|
-
hasHandoff: await fileExists(
|
|
4471
|
-
hasMemory: await fileExists(
|
|
4583
|
+
hasPlans: await fileExists(path19.join(rootDir, ".cursor/plans")),
|
|
4584
|
+
hasHandoff: await fileExists(path19.join(rootDir, ".cursor/HANDOFF.md")),
|
|
4585
|
+
hasMemory: await fileExists(path19.join(rootDir, ".cursor/memory"))
|
|
4472
4586
|
};
|
|
4473
4587
|
}
|
|
4474
4588
|
var REQUIRED_SECRET_PATTERNS = [
|
|
@@ -4489,9 +4603,9 @@ var KIT_OWNED_IGNORE_PATTERNS = [
|
|
|
4489
4603
|
".cursor/context/mission-timing.json"
|
|
4490
4604
|
];
|
|
4491
4605
|
async function detectSafety(rootDir, trackedFiles) {
|
|
4492
|
-
const gitignorePath =
|
|
4606
|
+
const gitignorePath = path19.join(rootDir, ".gitignore");
|
|
4493
4607
|
const hasGitignore = await fileExists(gitignorePath);
|
|
4494
|
-
const gitignore = hasGitignore ? await
|
|
4608
|
+
const gitignore = hasGitignore ? await readFile12(gitignorePath, "utf8") : "";
|
|
4495
4609
|
const lines = gitignore.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
4496
4610
|
const ignoredSecretPatterns = REQUIRED_SECRET_PATTERNS.filter(
|
|
4497
4611
|
(pattern) => lines.includes(pattern)
|
|
@@ -4500,7 +4614,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
4500
4614
|
(file) => /(^|\/)(\.env(\..+)?|.*\.(key|pem|p12|pfx)|.*credentials.*\.json)$/i.test(file)
|
|
4501
4615
|
);
|
|
4502
4616
|
const hookPaths = [".husky", ".git/hooks/pre-commit", "git-hooks/pre-commit"];
|
|
4503
|
-
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(
|
|
4617
|
+
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(path19.join(rootDir, item))))).some(Boolean);
|
|
4504
4618
|
const guardCandidates = [
|
|
4505
4619
|
".husky/pre-commit",
|
|
4506
4620
|
".husky/pre-push",
|
|
@@ -4509,7 +4623,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
4509
4623
|
];
|
|
4510
4624
|
const guardContents = await Promise.all(
|
|
4511
4625
|
guardCandidates.map(
|
|
4512
|
-
async (item) => await fileExists(
|
|
4626
|
+
async (item) => await fileExists(path19.join(rootDir, item)) ? readFile12(path19.join(rootDir, item), "utf8") : ""
|
|
4513
4627
|
)
|
|
4514
4628
|
);
|
|
4515
4629
|
return {
|
|
@@ -4527,11 +4641,11 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
4527
4641
|
}
|
|
4528
4642
|
|
|
4529
4643
|
// src/scanner/scan.ts
|
|
4530
|
-
import
|
|
4644
|
+
import path25 from "path";
|
|
4531
4645
|
|
|
4532
4646
|
// src/scanner/detect-git.ts
|
|
4533
4647
|
import { execFile as execFile4 } from "child_process";
|
|
4534
|
-
import
|
|
4648
|
+
import path20 from "path";
|
|
4535
4649
|
import { promisify as promisify4 } from "util";
|
|
4536
4650
|
var exec = promisify4(execFile4);
|
|
4537
4651
|
function remoteHostname(remoteUrl) {
|
|
@@ -4555,7 +4669,7 @@ function sanitizeRemoteUrl(remoteUrl) {
|
|
|
4555
4669
|
}
|
|
4556
4670
|
async function detectProvider(rootDir, remoteUrl) {
|
|
4557
4671
|
const configuration = await readJson(
|
|
4558
|
-
|
|
4672
|
+
path20.join(rootDir, REPOSITORY_PROFILE_REL)
|
|
4559
4673
|
);
|
|
4560
4674
|
const configuredProvider = configuration?.git?.provider;
|
|
4561
4675
|
if (configuredProvider) {
|
|
@@ -4564,7 +4678,7 @@ async function detectProvider(rootDir, remoteUrl) {
|
|
|
4564
4678
|
provider: configuredProvider,
|
|
4565
4679
|
providerKind: configuredKind,
|
|
4566
4680
|
confidence: "high",
|
|
4567
|
-
evidence: [{ source: "configuration", value:
|
|
4681
|
+
evidence: [{ source: "configuration", value: `${REPOSITORY_PROFILE_REL}#git.provider` }]
|
|
4568
4682
|
};
|
|
4569
4683
|
}
|
|
4570
4684
|
if (!remoteUrl) {
|
|
@@ -4612,7 +4726,7 @@ async function detectProvider(rootDir, remoteUrl) {
|
|
|
4612
4726
|
evidence: remoteEvidence
|
|
4613
4727
|
};
|
|
4614
4728
|
}
|
|
4615
|
-
if (await fileExists(
|
|
4729
|
+
if (await fileExists(path20.join(rootDir, ".gitlab-ci.yml"))) {
|
|
4616
4730
|
return {
|
|
4617
4731
|
provider: "gitlab",
|
|
4618
4732
|
providerKind: "gitlab-self-hosted",
|
|
@@ -4702,11 +4816,11 @@ async function detectGit(rootDir) {
|
|
|
4702
4816
|
}
|
|
4703
4817
|
|
|
4704
4818
|
// src/scanner/detect-ide.ts
|
|
4705
|
-
import
|
|
4819
|
+
import path21 from "path";
|
|
4706
4820
|
async function detectIde(rootDir) {
|
|
4707
|
-
const hasCursor = await fileExists(
|
|
4708
|
-
const hasVSCode = await fileExists(
|
|
4709
|
-
const hasWindsurf = await fileExists(
|
|
4821
|
+
const hasCursor = await fileExists(path21.join(rootDir, ".cursor"));
|
|
4822
|
+
const hasVSCode = await fileExists(path21.join(rootDir, ".vscode"));
|
|
4823
|
+
const hasWindsurf = await fileExists(path21.join(rootDir, ".windsurfrules"));
|
|
4710
4824
|
if (hasCursor) return { ide: "cursor", plan: "cursor-pro" };
|
|
4711
4825
|
if (hasVSCode) return { ide: "vscode", plan: "vscode-pro" };
|
|
4712
4826
|
if (hasWindsurf) return { ide: "windsurf", plan: "windsurf" };
|
|
@@ -4714,8 +4828,8 @@ async function detectIde(rootDir) {
|
|
|
4714
4828
|
}
|
|
4715
4829
|
|
|
4716
4830
|
// src/scanner/detect-infra.ts
|
|
4717
|
-
import { readFile as
|
|
4718
|
-
import
|
|
4831
|
+
import { readFile as readFile13, readdir as readdir5 } from "fs/promises";
|
|
4832
|
+
import path22 from "path";
|
|
4719
4833
|
|
|
4720
4834
|
// src/types.ts
|
|
4721
4835
|
var GIT_PLATFORM_META = {
|
|
@@ -4795,7 +4909,7 @@ function extractRunCommands(yaml) {
|
|
|
4795
4909
|
return Array.from(new Set(commands));
|
|
4796
4910
|
}
|
|
4797
4911
|
async function detectCiRunCommands(rootDir) {
|
|
4798
|
-
const workflowsDir =
|
|
4912
|
+
const workflowsDir = path22.join(rootDir, ".github", "workflows");
|
|
4799
4913
|
if (!await fileExists(workflowsDir)) return [];
|
|
4800
4914
|
let entries;
|
|
4801
4915
|
try {
|
|
@@ -4807,7 +4921,7 @@ async function detectCiRunCommands(rootDir) {
|
|
|
4807
4921
|
const perFile = await Promise.all(
|
|
4808
4922
|
workflowFiles.map(async (name) => {
|
|
4809
4923
|
try {
|
|
4810
|
-
return extractRunCommands(await
|
|
4924
|
+
return extractRunCommands(await readFile13(path22.join(workflowsDir, name), "utf8"));
|
|
4811
4925
|
} catch {
|
|
4812
4926
|
return [];
|
|
4813
4927
|
}
|
|
@@ -4816,12 +4930,12 @@ async function detectCiRunCommands(rootDir) {
|
|
|
4816
4930
|
return Array.from(new Set(perFile.flat()));
|
|
4817
4931
|
}
|
|
4818
4932
|
async function detectInfra(rootDir) {
|
|
4819
|
-
const docker = await fileExists(
|
|
4820
|
-
const kubernetes = await fileExists(
|
|
4933
|
+
const docker = await fileExists(path22.join(rootDir, "Dockerfile")) || await fileExists(path22.join(rootDir, "docker-compose.yml")) || await fileExists(path22.join(rootDir, "docker-compose.yaml"));
|
|
4934
|
+
const kubernetes = await fileExists(path22.join(rootDir, "k8s")) || await fileExists(path22.join(rootDir, "kubernetes"));
|
|
4821
4935
|
let ci = "none";
|
|
4822
4936
|
const ciFiles = [];
|
|
4823
4937
|
for (const [platform, filePath] of Object.entries(CI_PLATFORM_FILES)) {
|
|
4824
|
-
if (await fileExists(
|
|
4938
|
+
if (await fileExists(path22.join(rootDir, filePath))) {
|
|
4825
4939
|
if (ci === "none") ci = platform;
|
|
4826
4940
|
ciFiles.push(filePath);
|
|
4827
4941
|
}
|
|
@@ -4846,12 +4960,12 @@ async function detectInfra(rootDir) {
|
|
|
4846
4960
|
];
|
|
4847
4961
|
const infrastructureFiles = (await Promise.all(
|
|
4848
4962
|
infrastructureCandidates.map(
|
|
4849
|
-
async (file) => await fileExists(
|
|
4963
|
+
async (file) => await fileExists(path22.join(rootDir, file)) ? file : void 0
|
|
4850
4964
|
)
|
|
4851
4965
|
)).filter((file) => file !== void 0);
|
|
4852
4966
|
const deploymentFiles = (await Promise.all(
|
|
4853
4967
|
deploymentCandidates.map(
|
|
4854
|
-
async (file) => await fileExists(
|
|
4968
|
+
async (file) => await fileExists(path22.join(rootDir, file)) ? file : void 0
|
|
4855
4969
|
)
|
|
4856
4970
|
)).filter((file) => file !== void 0);
|
|
4857
4971
|
const ciRunCommands = ci === "github-actions" ? await detectCiRunCommands(rootDir) : [];
|
|
@@ -4859,18 +4973,18 @@ async function detectInfra(rootDir) {
|
|
|
4859
4973
|
}
|
|
4860
4974
|
|
|
4861
4975
|
// src/scanner/detect-services.ts
|
|
4862
|
-
import { readFile as
|
|
4863
|
-
import
|
|
4976
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
4977
|
+
import path23 from "path";
|
|
4864
4978
|
async function detectProjectManagement(rootDir) {
|
|
4865
4979
|
const tools = [];
|
|
4866
4980
|
const mcpConfigPaths = [
|
|
4867
|
-
|
|
4868
|
-
|
|
4981
|
+
path23.join(rootDir, ".cursor", "mcp.json"),
|
|
4982
|
+
path23.join(rootDir, "mcp.json")
|
|
4869
4983
|
];
|
|
4870
4984
|
for (const configPath of mcpConfigPaths) {
|
|
4871
4985
|
if (!await fileExists(configPath)) continue;
|
|
4872
4986
|
try {
|
|
4873
|
-
const raw = await
|
|
4987
|
+
const raw = await readFile14(configPath, "utf8");
|
|
4874
4988
|
const lower = raw.toLowerCase();
|
|
4875
4989
|
if (lower.includes("clickup")) tools.push("clickup");
|
|
4876
4990
|
if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
|
|
@@ -4881,20 +4995,20 @@ async function detectProjectManagement(rootDir) {
|
|
|
4881
4995
|
} catch {
|
|
4882
4996
|
}
|
|
4883
4997
|
}
|
|
4884
|
-
if (await fileExists(
|
|
4998
|
+
if (await fileExists(path23.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
|
|
4885
4999
|
tools.push("github-issues");
|
|
4886
5000
|
}
|
|
4887
|
-
if (await fileExists(
|
|
5001
|
+
if (await fileExists(path23.join(rootDir, ".github", "projects"))) {
|
|
4888
5002
|
tools.push("github-projects");
|
|
4889
5003
|
}
|
|
4890
5004
|
return [...new Set(tools)];
|
|
4891
5005
|
}
|
|
4892
5006
|
async function detectServices(rootDir) {
|
|
4893
|
-
const hasPrisma = await fileExists(
|
|
4894
|
-
const hasSequelize = await fileExists(
|
|
4895
|
-
const hasDrizzle = await fileExists(
|
|
4896
|
-
const hasKnex = await fileExists(
|
|
4897
|
-
const hasTypeorm = await fileExists(
|
|
5007
|
+
const hasPrisma = await fileExists(path23.join(rootDir, "prisma/schema.prisma"));
|
|
5008
|
+
const hasSequelize = await fileExists(path23.join(rootDir, "sequelize"));
|
|
5009
|
+
const hasDrizzle = await fileExists(path23.join(rootDir, "drizzle.config.ts"));
|
|
5010
|
+
const hasKnex = await fileExists(path23.join(rootDir, "knexfile.ts"));
|
|
5011
|
+
const hasTypeorm = await fileExists(path23.join(rootDir, "ormconfig.json"));
|
|
4898
5012
|
const database = hasPrisma || hasSequelize || hasDrizzle || hasKnex || hasTypeorm ? "postgresql" : void 0;
|
|
4899
5013
|
const orm = hasPrisma ? "prisma" : hasDrizzle ? "drizzle" : hasSequelize ? "sequelize" : hasKnex ? "knex" : hasTypeorm ? "typeorm" : void 0;
|
|
4900
5014
|
const projectManagement = await detectProjectManagement(rootDir);
|
|
@@ -4906,8 +5020,8 @@ async function detectServices(rootDir) {
|
|
|
4906
5020
|
}
|
|
4907
5021
|
|
|
4908
5022
|
// src/scanner/detect-stack.ts
|
|
4909
|
-
import { readFile as
|
|
4910
|
-
import
|
|
5023
|
+
import { readFile as readFile15, readdir as readdir6 } from "fs/promises";
|
|
5024
|
+
import path24 from "path";
|
|
4911
5025
|
var PROJECT_MARKERS = [
|
|
4912
5026
|
"package.json",
|
|
4913
5027
|
"requirements.txt",
|
|
@@ -4933,7 +5047,7 @@ async function scanSubdirectoryMarkers(rootDir) {
|
|
|
4933
5047
|
const candidates = dirs.flatMap((dir) => PROJECT_MARKERS.map((marker) => ({ dir, marker })));
|
|
4934
5048
|
const found = await Promise.all(
|
|
4935
5049
|
candidates.map(async (candidate2) => {
|
|
4936
|
-
const exists2 = await fileExists(
|
|
5050
|
+
const exists2 = await fileExists(path24.join(rootDir, candidate2.dir, candidate2.marker));
|
|
4937
5051
|
return exists2 ? candidate2 : null;
|
|
4938
5052
|
})
|
|
4939
5053
|
);
|
|
@@ -4949,7 +5063,7 @@ var DART_VALIDATION_COMMANDS = [
|
|
|
4949
5063
|
async function detectDartFlavor(pubspecDir) {
|
|
4950
5064
|
let content = "";
|
|
4951
5065
|
try {
|
|
4952
|
-
content = await
|
|
5066
|
+
content = await readFile15(path24.join(pubspecDir, "pubspec.yaml"), "utf8");
|
|
4953
5067
|
} catch {
|
|
4954
5068
|
}
|
|
4955
5069
|
return { isFlutter: isFlutterPubspec(content) };
|
|
@@ -4962,7 +5076,7 @@ async function detectStackFromSubdirHits(rootDir, hits) {
|
|
|
4962
5076
|
const dartHits = hits.filter((hit) => hit.marker === "pubspec.yaml");
|
|
4963
5077
|
if (dartHits.length > 0) {
|
|
4964
5078
|
const flavors = await Promise.all(
|
|
4965
|
-
dartHits.map((hit) => detectDartFlavor(
|
|
5079
|
+
dartHits.map((hit) => detectDartFlavor(path24.join(rootDir, hit.dir)))
|
|
4966
5080
|
);
|
|
4967
5081
|
const testCommands = Array.from(
|
|
4968
5082
|
new Set(flavors.map((flavor) => flavor.isFlutter ? "flutter test" : "dart test"))
|
|
@@ -4979,8 +5093,8 @@ async function detectStackFromSubdirHits(rootDir, hits) {
|
|
|
4979
5093
|
}
|
|
4980
5094
|
const nodeHit = hits.find((hit) => hit.marker === "package.json");
|
|
4981
5095
|
if (nodeHit) {
|
|
4982
|
-
const subRoot =
|
|
4983
|
-
const packageJson = await readJson(
|
|
5096
|
+
const subRoot = path24.join(rootDir, nodeHit.dir);
|
|
5097
|
+
const packageJson = await readJson(path24.join(subRoot, "package.json")) ?? {};
|
|
4984
5098
|
const scripts = packageJson.scripts ?? {};
|
|
4985
5099
|
const packageManager = await detectPackageManager(subRoot, packageJson);
|
|
4986
5100
|
const commands = commandsForScripts(scripts, packageManager.packageManager);
|
|
@@ -5037,7 +5151,7 @@ function isDetectionEvidenceArray(value) {
|
|
|
5037
5151
|
}
|
|
5038
5152
|
async function readConfirmedStack(rootDir) {
|
|
5039
5153
|
const configuration = await readJson(
|
|
5040
|
-
|
|
5154
|
+
path24.join(rootDir, REPOSITORY_PROFILE_REL)
|
|
5041
5155
|
);
|
|
5042
5156
|
const configured = configuration?.stack;
|
|
5043
5157
|
const language = configured?.language;
|
|
@@ -5051,7 +5165,7 @@ async function readConfirmedStack(rootDir) {
|
|
|
5051
5165
|
const scripts = configured.scripts && typeof configured.scripts === "object" && !Array.isArray(configured.scripts) ? configured.scripts : void 0;
|
|
5052
5166
|
const packageManagerEvidence = isDetectionEvidenceArray(
|
|
5053
5167
|
configured.packageManagerEvidence
|
|
5054
|
-
) ? configured.packageManagerEvidence : [{ source: "configuration", value:
|
|
5168
|
+
) ? configured.packageManagerEvidence : [{ source: "configuration", value: `${REPOSITORY_PROFILE_REL}#stack.language` }];
|
|
5055
5169
|
return {
|
|
5056
5170
|
language,
|
|
5057
5171
|
framework: typeof configured.framework === "string" ? configured.framework : void 0,
|
|
@@ -5080,7 +5194,7 @@ async function detectPackageManager(rootDir, packageJson) {
|
|
|
5080
5194
|
};
|
|
5081
5195
|
}
|
|
5082
5196
|
for (const [lockfile, packageManager] of LOCKFILES) {
|
|
5083
|
-
if (await fileExists(
|
|
5197
|
+
if (await fileExists(path24.join(rootDir, lockfile))) {
|
|
5084
5198
|
return {
|
|
5085
5199
|
packageManager,
|
|
5086
5200
|
evidence: [{ source: "file", value: lockfile }]
|
|
@@ -5099,33 +5213,33 @@ function commandsForScripts(scripts, packageManager) {
|
|
|
5099
5213
|
async function detectStack(rootDir) {
|
|
5100
5214
|
const confirmed = await readConfirmedStack(rootDir);
|
|
5101
5215
|
if (confirmed) return confirmed;
|
|
5102
|
-
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(
|
|
5216
|
+
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(path24.join(rootDir, item))))).some(Boolean);
|
|
5103
5217
|
if (!hasAnyProjectMarker) {
|
|
5104
5218
|
const subdirHits = await scanSubdirectoryMarkers(rootDir);
|
|
5105
5219
|
if (subdirHits.length > 0) {
|
|
5106
5220
|
return detectStackFromSubdirHits(rootDir, subdirHits);
|
|
5107
5221
|
}
|
|
5108
5222
|
}
|
|
5109
|
-
const hasPackageJson = await fileExists(
|
|
5223
|
+
const hasPackageJson = await fileExists(path24.join(rootDir, "package.json"));
|
|
5110
5224
|
if (hasPackageJson) {
|
|
5111
|
-
const packageJson = await readJson(
|
|
5225
|
+
const packageJson = await readJson(path24.join(rootDir, "package.json")) ?? {};
|
|
5112
5226
|
const scripts = packageJson.scripts ?? {};
|
|
5113
5227
|
const packageManager = await detectPackageManager(rootDir, packageJson);
|
|
5114
5228
|
const commands = commandsForScripts(scripts, packageManager.packageManager);
|
|
5115
|
-
const hasNextConfig = await fileExists(
|
|
5116
|
-
const hasNestConfig = await fileExists(
|
|
5229
|
+
const hasNextConfig = await fileExists(path24.join(rootDir, "next.config.js")) || await fileExists(path24.join(rootDir, "next.config.mjs")) || await fileExists(path24.join(rootDir, "next.config.ts"));
|
|
5230
|
+
const hasNestConfig = await fileExists(path24.join(rootDir, "nest-cli.json"));
|
|
5117
5231
|
return {
|
|
5118
5232
|
language: "node",
|
|
5119
5233
|
framework: hasNextConfig ? "nextjs" : hasNestConfig ? "nestjs" : "node",
|
|
5120
5234
|
packageManager: packageManager.packageManager,
|
|
5121
5235
|
packageManagerEvidence: packageManager.evidence,
|
|
5122
5236
|
scripts,
|
|
5123
|
-
workspaces: packageJson.workspaces !== void 0 || await fileExists(
|
|
5237
|
+
workspaces: packageJson.workspaces !== void 0 || await fileExists(path24.join(rootDir, "pnpm-workspace.yaml")),
|
|
5124
5238
|
...commands,
|
|
5125
5239
|
hasProjectFiles: hasAnyProjectMarker
|
|
5126
5240
|
};
|
|
5127
5241
|
}
|
|
5128
|
-
if (await fileExists(
|
|
5242
|
+
if (await fileExists(path24.join(rootDir, "pyproject.toml"))) {
|
|
5129
5243
|
return {
|
|
5130
5244
|
language: "python",
|
|
5131
5245
|
framework: "python",
|
|
@@ -5135,7 +5249,7 @@ async function detectStack(rootDir) {
|
|
|
5135
5249
|
hasProjectFiles: hasAnyProjectMarker
|
|
5136
5250
|
};
|
|
5137
5251
|
}
|
|
5138
|
-
if (await fileExists(
|
|
5252
|
+
if (await fileExists(path24.join(rootDir, "go.mod"))) {
|
|
5139
5253
|
return {
|
|
5140
5254
|
language: "go",
|
|
5141
5255
|
framework: "go",
|
|
@@ -5145,7 +5259,7 @@ async function detectStack(rootDir) {
|
|
|
5145
5259
|
hasProjectFiles: hasAnyProjectMarker
|
|
5146
5260
|
};
|
|
5147
5261
|
}
|
|
5148
|
-
if (await fileExists(
|
|
5262
|
+
if (await fileExists(path24.join(rootDir, "Cargo.toml"))) {
|
|
5149
5263
|
return {
|
|
5150
5264
|
language: "rust",
|
|
5151
5265
|
framework: "rust",
|
|
@@ -5155,7 +5269,7 @@ async function detectStack(rootDir) {
|
|
|
5155
5269
|
hasProjectFiles: hasAnyProjectMarker
|
|
5156
5270
|
};
|
|
5157
5271
|
}
|
|
5158
|
-
if (await fileExists(
|
|
5272
|
+
if (await fileExists(path24.join(rootDir, "composer.json"))) {
|
|
5159
5273
|
return {
|
|
5160
5274
|
language: "php",
|
|
5161
5275
|
framework: "php",
|
|
@@ -5165,7 +5279,7 @@ async function detectStack(rootDir) {
|
|
|
5165
5279
|
hasProjectFiles: hasAnyProjectMarker
|
|
5166
5280
|
};
|
|
5167
5281
|
}
|
|
5168
|
-
if (await fileExists(
|
|
5282
|
+
if (await fileExists(path24.join(rootDir, "pubspec.yaml"))) {
|
|
5169
5283
|
const { isFlutter } = await detectDartFlavor(rootDir);
|
|
5170
5284
|
return {
|
|
5171
5285
|
language: "dart",
|
|
@@ -5199,7 +5313,7 @@ function isGreenfieldByEntries(entries) {
|
|
|
5199
5313
|
return meaningful.length === 0;
|
|
5200
5314
|
}
|
|
5201
5315
|
async function runScanner(rootDir) {
|
|
5202
|
-
const normalizedRoot =
|
|
5316
|
+
const normalizedRoot = path25.resolve(rootDir);
|
|
5203
5317
|
const entries = await listDirectory(normalizedRoot);
|
|
5204
5318
|
const stack = await detectStack(normalizedRoot);
|
|
5205
5319
|
const purpose = await detectPurpose(normalizedRoot, stack);
|
|
@@ -5237,8 +5351,7 @@ async function runScanner(rootDir) {
|
|
|
5237
5351
|
}
|
|
5238
5352
|
|
|
5239
5353
|
// src/scanner/safe-fixes.ts
|
|
5240
|
-
var
|
|
5241
|
-
var CONTEXT_CONFIG_RELATIVE_PATH = ".cursor/context/config.json";
|
|
5354
|
+
var CONTEXT_CONFIG_REL_POSIX = CONTEXT_CONFIG_REL.replace(/\\/g, "/");
|
|
5242
5355
|
var ESSENTIAL_DIRECTORIES = [
|
|
5243
5356
|
".cursor",
|
|
5244
5357
|
".cursor/context",
|
|
@@ -5423,7 +5536,7 @@ async function refreshRepositoryProfile(rootDir, options) {
|
|
|
5423
5536
|
generatedAt
|
|
5424
5537
|
});
|
|
5425
5538
|
const desiredProfile = createProfile(scan, report, generatedAt);
|
|
5426
|
-
const profilePath =
|
|
5539
|
+
const profilePath = path26.join(scan.rootDir, REPOSITORY_PROFILE_REL);
|
|
5427
5540
|
const existingProfile = await readJson(profilePath) ?? {};
|
|
5428
5541
|
const reconciled = reconcileFreshOverExisting(desiredProfile, existingProfile);
|
|
5429
5542
|
const meaningfulChange = !jsonEqual(
|
|
@@ -5446,7 +5559,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5446
5559
|
});
|
|
5447
5560
|
const changes = [];
|
|
5448
5561
|
for (const relativePath of ESSENTIAL_DIRECTORIES) {
|
|
5449
|
-
const absolutePath =
|
|
5562
|
+
const absolutePath = path26.join(beforeScan.rootDir, relativePath);
|
|
5450
5563
|
const exists2 = await fileExists(absolutePath);
|
|
5451
5564
|
if (!exists2 && !dryRun) await ensureDir(absolutePath);
|
|
5452
5565
|
recordChange(
|
|
@@ -5459,14 +5572,14 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5459
5572
|
);
|
|
5460
5573
|
}
|
|
5461
5574
|
const gitignoreRelativePath = ".gitignore";
|
|
5462
|
-
const gitignorePath =
|
|
5463
|
-
const existingGitignore = await fileExists(gitignorePath) ? await
|
|
5575
|
+
const gitignorePath = path26.join(beforeScan.rootDir, gitignoreRelativePath);
|
|
5576
|
+
const existingGitignore = await fileExists(gitignorePath) ? await readFile16(gitignorePath, "utf8") : "";
|
|
5464
5577
|
const gitignoreAfterSecrets = mergeSecretIgnores(existingGitignore);
|
|
5465
5578
|
const secretsChanged = gitignoreAfterSecrets !== existingGitignore;
|
|
5466
5579
|
const mergedGitignore = mergeKitOwnedIgnores(gitignoreAfterSecrets);
|
|
5467
5580
|
const gitignoreChanged = mergedGitignore !== existingGitignore;
|
|
5468
5581
|
const kitOwnedChanged = mergedGitignore !== gitignoreAfterSecrets;
|
|
5469
|
-
if (gitignoreChanged && !dryRun) await
|
|
5582
|
+
if (gitignoreChanged && !dryRun) await writeFile6(gitignorePath, mergedGitignore, "utf8");
|
|
5470
5583
|
recordChange(
|
|
5471
5584
|
changes,
|
|
5472
5585
|
"merge-secret-ignores",
|
|
@@ -5489,7 +5602,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5489
5602
|
kitOwnedChanged ? "kit-owned session/derived paths are missing" : "kit-owned session/derived paths are present"
|
|
5490
5603
|
)
|
|
5491
5604
|
);
|
|
5492
|
-
const profilePath =
|
|
5605
|
+
const profilePath = path26.join(beforeScan.rootDir, REPOSITORY_PROFILE_REL);
|
|
5493
5606
|
const existingProfile = await readJson(profilePath) ?? {};
|
|
5494
5607
|
const desiredProfile = createProfile(beforeScan, before, generatedAt);
|
|
5495
5608
|
const mergedProfile = mergeMissing(existingProfile, desiredProfile);
|
|
@@ -5498,11 +5611,11 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5498
5611
|
recordChange(
|
|
5499
5612
|
changes,
|
|
5500
5613
|
"merge-repository-profile",
|
|
5501
|
-
|
|
5614
|
+
REPOSITORY_PROFILE_REL,
|
|
5502
5615
|
profileChanged,
|
|
5503
5616
|
dryRun,
|
|
5504
5617
|
relativeEvidence(
|
|
5505
|
-
|
|
5618
|
+
REPOSITORY_PROFILE_REL,
|
|
5506
5619
|
profileChanged ? "missing scanner-derived facts" : "existing facts preserved"
|
|
5507
5620
|
)
|
|
5508
5621
|
);
|
|
@@ -5511,22 +5624,22 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5511
5624
|
generatorVersion: options.generatorVersion,
|
|
5512
5625
|
generatedAt
|
|
5513
5626
|
});
|
|
5514
|
-
const
|
|
5515
|
-
const existingContextConfig = await readJson(
|
|
5627
|
+
const configPath = contextConfigPath(beforeScan.rootDir);
|
|
5628
|
+
const existingContextConfig = await readJson(configPath) ?? {};
|
|
5516
5629
|
const onboarding = reconcileOnboardingState(evidenceReport, existingContextConfig, generatedAt);
|
|
5517
5630
|
const defaults = preferenceDefaults(onboarding, existingContextConfig.onboarded);
|
|
5518
5631
|
const mergedContextConfig = mergeMissing(existingContextConfig, defaults);
|
|
5519
5632
|
mergedContextConfig.onboarding = onboarding;
|
|
5520
5633
|
const contextConfigChanged = !jsonEqual(existingContextConfig, mergedContextConfig);
|
|
5521
|
-
if (contextConfigChanged && !dryRun) await writeJson(
|
|
5634
|
+
if (contextConfigChanged && !dryRun) await writeJson(configPath, mergedContextConfig);
|
|
5522
5635
|
recordChange(
|
|
5523
5636
|
changes,
|
|
5524
5637
|
"merge-onboarding-state",
|
|
5525
|
-
|
|
5638
|
+
CONTEXT_CONFIG_REL_POSIX,
|
|
5526
5639
|
contextConfigChanged,
|
|
5527
5640
|
dryRun,
|
|
5528
5641
|
relativeEvidence(
|
|
5529
|
-
|
|
5642
|
+
CONTEXT_CONFIG_REL_POSIX,
|
|
5530
5643
|
contextConfigChanged ? "missing preferences or onboarding state" : "state already merged"
|
|
5531
5644
|
)
|
|
5532
5645
|
);
|
|
@@ -5540,10 +5653,10 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5540
5653
|
}
|
|
5541
5654
|
|
|
5542
5655
|
// src/scanner/snapshot.ts
|
|
5543
|
-
import
|
|
5656
|
+
import path27 from "path";
|
|
5544
5657
|
var READINESS_SNAPSHOT_RELATIVE_PATH = ".cursor/context/readiness.json";
|
|
5545
5658
|
async function writeReadinessSnapshot(rootDir, report) {
|
|
5546
|
-
const snapshotPath =
|
|
5659
|
+
const snapshotPath = path27.join(rootDir, READINESS_SNAPSHOT_RELATIVE_PATH);
|
|
5547
5660
|
await writeJson(snapshotPath, report);
|
|
5548
5661
|
return snapshotPath;
|
|
5549
5662
|
}
|
|
@@ -5735,7 +5848,7 @@ async function withCliProgress(label, fn, opts) {
|
|
|
5735
5848
|
// src/commands/doctor.ts
|
|
5736
5849
|
async function readDeferredEssentials(rootDir, report) {
|
|
5737
5850
|
const config = await readJson(
|
|
5738
|
-
|
|
5851
|
+
path28.join(rootDir, ".cursor", "context", "config.json")
|
|
5739
5852
|
);
|
|
5740
5853
|
const deferredItems = config?.onboarding?.deferredItems;
|
|
5741
5854
|
if (!Array.isArray(deferredItems)) return [];
|
|
@@ -5747,7 +5860,7 @@ async function readDeferredEssentials(rootDir, report) {
|
|
|
5747
5860
|
).map((item) => ({ checkId: item.checkId, reason: item.reason }));
|
|
5748
5861
|
}
|
|
5749
5862
|
async function runDoctor(cwd, options = {}) {
|
|
5750
|
-
const rootDir =
|
|
5863
|
+
const rootDir = path28.resolve(cwd);
|
|
5751
5864
|
const hooks = await assessHooksHealth(rootDir);
|
|
5752
5865
|
const env = await assessEnvironment();
|
|
5753
5866
|
if (options.fixSafe) {
|
|
@@ -6275,8 +6388,8 @@ var guardCommand = defineCommand8({
|
|
|
6275
6388
|
|
|
6276
6389
|
// src/commands/handoff.ts
|
|
6277
6390
|
import { spawn as spawn4 } from "child_process";
|
|
6278
|
-
import { appendFile, readFile as
|
|
6279
|
-
import
|
|
6391
|
+
import { appendFile, readFile as readFile17, readdir as readdir7, writeFile as writeFile7 } from "fs/promises";
|
|
6392
|
+
import path29 from "path";
|
|
6280
6393
|
import { defineCommand as defineCommand9 } from "citty";
|
|
6281
6394
|
|
|
6282
6395
|
// src/invariants/handoff-prune.ts
|
|
@@ -6417,12 +6530,12 @@ function pruneArchiveTimestamp(now2 = /* @__PURE__ */ new Date()) {
|
|
|
6417
6530
|
return `${now2.getFullYear()}-${pad(now2.getMonth() + 1)}-${pad(now2.getDate())}-${pad(now2.getHours())}${pad(now2.getMinutes())}`;
|
|
6418
6531
|
}
|
|
6419
6532
|
async function runPrune(cwd, keep) {
|
|
6420
|
-
const handoffPath =
|
|
6533
|
+
const handoffPath = path29.join(cwd, ".cursor", "HANDOFF.md");
|
|
6421
6534
|
if (!await fileExists(handoffPath)) {
|
|
6422
6535
|
logger.warn("No .cursor/HANDOFF.md to prune.");
|
|
6423
6536
|
return;
|
|
6424
6537
|
}
|
|
6425
|
-
const original = await
|
|
6538
|
+
const original = await readFile17(handoffPath, "utf8");
|
|
6426
6539
|
const result = pruneHandoffText(original, keep);
|
|
6427
6540
|
if (!result.ok) {
|
|
6428
6541
|
logger.warn(`Refusing to prune: ${result.reason}`);
|
|
@@ -6435,9 +6548,9 @@ async function runPrune(cwd, keep) {
|
|
|
6435
6548
|
);
|
|
6436
6549
|
return;
|
|
6437
6550
|
}
|
|
6438
|
-
const archiveDir =
|
|
6551
|
+
const archiveDir = path29.join(cwd, ".cursor", "context", "archive");
|
|
6439
6552
|
await ensureDir(archiveDir);
|
|
6440
|
-
const archivePath =
|
|
6553
|
+
const archivePath = path29.join(archiveDir, `handoff-${pruneArchiveTimestamp()}.md`);
|
|
6441
6554
|
const header = [
|
|
6442
6555
|
`<!-- Pruned from .cursor/HANDOFF.md at ${(/* @__PURE__ */ new Date()).toISOString()} -->`,
|
|
6443
6556
|
`<!-- ${result.archived.length} narrative ${result.archived.length === 1 ? "entry" : "entries"} archived, keep=${keep} -->`,
|
|
@@ -6449,11 +6562,11 @@ async function runPrune(cwd, keep) {
|
|
|
6449
6562
|
await appendFile(archivePath, `
|
|
6450
6563
|
${header}${body}`, "utf8");
|
|
6451
6564
|
} else {
|
|
6452
|
-
await
|
|
6565
|
+
await writeFile7(archivePath, `${header}${body}`, "utf8");
|
|
6453
6566
|
}
|
|
6454
|
-
await
|
|
6567
|
+
await writeFile7(handoffPath, result.text ?? original, "utf8");
|
|
6455
6568
|
logger.success(
|
|
6456
|
-
`HANDOFF.md pruned: kept ${result.keptCount} narrative ${result.keptCount === 1 ? "entry" : "entries"}, archived ${result.archived.length} to ${
|
|
6569
|
+
`HANDOFF.md pruned: kept ${result.keptCount} narrative ${result.keptCount === 1 ? "entry" : "entries"}, archived ${result.archived.length} to ${path29.relative(cwd, archivePath)}.`
|
|
6457
6570
|
);
|
|
6458
6571
|
}
|
|
6459
6572
|
function parsePlanFrontmatter(raw) {
|
|
@@ -6474,19 +6587,19 @@ async function findActivePlan(plansDir) {
|
|
|
6474
6587
|
if (!await fileExists(plansDir)) return null;
|
|
6475
6588
|
const files = (await readdir7(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
|
|
6476
6589
|
for (const file of files) {
|
|
6477
|
-
const raw = await
|
|
6590
|
+
const raw = await readFile17(path29.join(plansDir, file), "utf8");
|
|
6478
6591
|
const fm = parsePlanFrontmatter(raw);
|
|
6479
6592
|
if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
|
|
6480
6593
|
return { file, raw };
|
|
6481
6594
|
}
|
|
6482
6595
|
}
|
|
6483
|
-
return files[0] ? { file: files[0], raw: await
|
|
6596
|
+
return files[0] ? { file: files[0], raw: await readFile17(path29.join(plansDir, files[0]), "utf8") } : null;
|
|
6484
6597
|
}
|
|
6485
6598
|
function now() {
|
|
6486
6599
|
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
|
|
6487
6600
|
}
|
|
6488
6601
|
async function loadProfile(rootDir) {
|
|
6489
|
-
const configPath =
|
|
6602
|
+
const configPath = path29.join(rootDir, ".cursor", "agent-kit.config.json");
|
|
6490
6603
|
try {
|
|
6491
6604
|
return await readJson(configPath);
|
|
6492
6605
|
} catch {
|
|
@@ -6601,15 +6714,15 @@ var handoffCommand = defineCommand9({
|
|
|
6601
6714
|
return;
|
|
6602
6715
|
}
|
|
6603
6716
|
const profile = await loadProfile(args.cwd);
|
|
6604
|
-
const plansDir =
|
|
6605
|
-
const handoffPath =
|
|
6717
|
+
const plansDir = path29.join(args.cwd, ".cursor", "plans");
|
|
6718
|
+
const handoffPath = path29.join(args.cwd, ".cursor", "HANDOFF.md");
|
|
6606
6719
|
const plan = await findActivePlan(plansDir);
|
|
6607
6720
|
if (plan) {
|
|
6608
6721
|
const fm = parsePlanFrontmatter(plan.raw);
|
|
6609
6722
|
if (fm) {
|
|
6610
|
-
await ensureDir(
|
|
6723
|
+
await ensureDir(path29.join(args.cwd, ".cursor"));
|
|
6611
6724
|
const content = buildHandoff(plan.file, fm, profile);
|
|
6612
|
-
await
|
|
6725
|
+
await writeFile7(handoffPath, content, "utf8");
|
|
6613
6726
|
logger.success("HANDOFF.md updated: .cursor/HANDOFF.md");
|
|
6614
6727
|
logger.info(
|
|
6615
6728
|
`Plan: ${plan.file} (${fm.todos?.filter((t) => t.status === "completed").length}/${fm.todos?.length} completed)`
|
|
@@ -6627,7 +6740,7 @@ var handoffCommand = defineCommand9({
|
|
|
6627
6740
|
}
|
|
6628
6741
|
logger.warn(`Plan ${plan.file} without valid frontmatter; trying legacy flow.`);
|
|
6629
6742
|
}
|
|
6630
|
-
const scriptPath =
|
|
6743
|
+
const scriptPath = path29.join(args.cwd, "cursor-handoff");
|
|
6631
6744
|
if (!await fileExists(scriptPath)) {
|
|
6632
6745
|
printV3Guidance();
|
|
6633
6746
|
return;
|
|
@@ -6652,7 +6765,7 @@ var handoffCommand = defineCommand9({
|
|
|
6652
6765
|
});
|
|
6653
6766
|
|
|
6654
6767
|
// src/commands/hook.ts
|
|
6655
|
-
import
|
|
6768
|
+
import path32 from "path";
|
|
6656
6769
|
import { defineCommand as defineCommand10 } from "citty";
|
|
6657
6770
|
|
|
6658
6771
|
// src/hooks/format-session-start.ts
|
|
@@ -6678,8 +6791,8 @@ function buildPreCompactUserMessage(payload = {}) {
|
|
|
6678
6791
|
|
|
6679
6792
|
// src/hooks/session-start.ts
|
|
6680
6793
|
import { spawn as spawn5 } from "child_process";
|
|
6681
|
-
import {
|
|
6682
|
-
import
|
|
6794
|
+
import { readFile as readFile19, stat as stat4 } from "fs/promises";
|
|
6795
|
+
import path31 from "path";
|
|
6683
6796
|
|
|
6684
6797
|
// src/invariants/handoff-schema.ts
|
|
6685
6798
|
var MACHINE_LIST_CHECKS = [
|
|
@@ -6721,8 +6834,8 @@ function validateHandoffText(text) {
|
|
|
6721
6834
|
}
|
|
6722
6835
|
|
|
6723
6836
|
// src/plan-index/plan-index.ts
|
|
6724
|
-
import { readFile as
|
|
6725
|
-
import
|
|
6837
|
+
import { readFile as readFile18, writeFile as writeFile8 } from "fs/promises";
|
|
6838
|
+
import path30 from "path";
|
|
6726
6839
|
var PLAN_INDEX_REL = ".cursor/context/plan-index.json";
|
|
6727
6840
|
var NONE = /^(none|n\/a|empty|nil)$/i;
|
|
6728
6841
|
var OPEN_STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress"]);
|
|
@@ -6730,14 +6843,14 @@ function defaultPlanIndexIo() {
|
|
|
6730
6843
|
return {
|
|
6731
6844
|
async readFile(absPath) {
|
|
6732
6845
|
try {
|
|
6733
|
-
return await
|
|
6846
|
+
return await readFile18(absPath, "utf8");
|
|
6734
6847
|
} catch {
|
|
6735
6848
|
return null;
|
|
6736
6849
|
}
|
|
6737
6850
|
},
|
|
6738
6851
|
async writeFile(absPath, body) {
|
|
6739
|
-
await ensureDir(
|
|
6740
|
-
await
|
|
6852
|
+
await ensureDir(path30.dirname(absPath));
|
|
6853
|
+
await writeFile8(absPath, body, "utf8");
|
|
6741
6854
|
}
|
|
6742
6855
|
};
|
|
6743
6856
|
}
|
|
@@ -6762,11 +6875,11 @@ function collectNamedPlanBasenames(named) {
|
|
|
6762
6875
|
return out;
|
|
6763
6876
|
}
|
|
6764
6877
|
function namedPlanCandidatePaths(root, basename) {
|
|
6765
|
-
const safe =
|
|
6878
|
+
const safe = path30.basename(basename);
|
|
6766
6879
|
if (!/^[A-Za-z0-9._-]+\.plan\.md$/i.test(safe)) return [];
|
|
6767
6880
|
return [
|
|
6768
|
-
|
|
6769
|
-
|
|
6881
|
+
path30.join(root, ".cursor", "plans", safe),
|
|
6882
|
+
path30.join(root, ".cursor", "plans", "archive", safe)
|
|
6770
6883
|
];
|
|
6771
6884
|
}
|
|
6772
6885
|
function classifyPlanRole(file, named, openTodos) {
|
|
@@ -6798,7 +6911,7 @@ function parsePlanFrontmatter2(raw) {
|
|
|
6798
6911
|
return { name, todos };
|
|
6799
6912
|
}
|
|
6800
6913
|
async function buildPlanIndex(root, io = defaultPlanIndexIo(), now2 = () => /* @__PURE__ */ new Date()) {
|
|
6801
|
-
const handoffPath =
|
|
6914
|
+
const handoffPath = path30.join(root, HANDOFF_REL);
|
|
6802
6915
|
const handoffText = await io.readFile(handoffPath) ?? "";
|
|
6803
6916
|
const named = extractHandoffNamedPlans(handoffText);
|
|
6804
6917
|
const plans = [];
|
|
@@ -6827,7 +6940,7 @@ async function buildPlanIndex(root, io = defaultPlanIndexIo(), now2 = () => /* @
|
|
|
6827
6940
|
}
|
|
6828
6941
|
async function writePlanIndex(root, io = defaultPlanIndexIo(), now2 = () => /* @__PURE__ */ new Date()) {
|
|
6829
6942
|
const index = await buildPlanIndex(root, io, now2);
|
|
6830
|
-
const dest =
|
|
6943
|
+
const dest = path30.join(root, PLAN_INDEX_REL);
|
|
6831
6944
|
const write = io.writeFile ?? defaultPlanIndexIo().writeFile;
|
|
6832
6945
|
if (write) {
|
|
6833
6946
|
await write(dest, `${JSON.stringify(index, null, 2)}
|
|
@@ -7066,19 +7179,11 @@ var NONE_PLACEHOLDERS = /* @__PURE__ */ new Set(["none", "n/a", "empty", "nil"])
|
|
|
7066
7179
|
var CURSOR_AWARENESS_SPAWN_TIMEOUT_MS = CHANGELOG_FETCH_TIMEOUT_MS + 3e3;
|
|
7067
7180
|
async function readFull(filePath) {
|
|
7068
7181
|
try {
|
|
7069
|
-
return await
|
|
7182
|
+
return await readFile19(filePath, "utf8");
|
|
7070
7183
|
} catch {
|
|
7071
7184
|
return "";
|
|
7072
7185
|
}
|
|
7073
7186
|
}
|
|
7074
|
-
async function fileExists2(p) {
|
|
7075
|
-
try {
|
|
7076
|
-
await access7(p);
|
|
7077
|
-
return true;
|
|
7078
|
-
} catch {
|
|
7079
|
-
return false;
|
|
7080
|
-
}
|
|
7081
|
-
}
|
|
7082
7187
|
function isMarkdownTableSeparator(line) {
|
|
7083
7188
|
const stripped = line.trim();
|
|
7084
7189
|
if (!stripped.startsWith("|")) return false;
|
|
@@ -7140,8 +7245,7 @@ function extractUnprocessedDogfoodLine(line) {
|
|
|
7140
7245
|
return raw;
|
|
7141
7246
|
}
|
|
7142
7247
|
async function l0Present(root) {
|
|
7143
|
-
|
|
7144
|
-
return await fileExists2(path29.join(cursor, "agent-kit.json")) || await fileExists2(path29.join(cursor, "commands", "agent-kit-onboard.md")) || await fileExists2(path29.join(cursor, "commands", "start-project.md"));
|
|
7248
|
+
return await fileExists(path31.join(root, MANIFEST_RELATIVE_PATH)) || await fileExists(path31.join(root, ".cursor", "commands", "agent-kit-onboard.md")) || await fileExists(path31.join(root, ".cursor", "commands", "start-project.md"));
|
|
7145
7249
|
}
|
|
7146
7250
|
function checkLabelAndRecommendation(check2) {
|
|
7147
7251
|
const checkId = check2.id;
|
|
@@ -7180,10 +7284,10 @@ function unresolvedReadinessChecks(data) {
|
|
|
7180
7284
|
return { essential, nonessential };
|
|
7181
7285
|
}
|
|
7182
7286
|
async function readinessSection(root) {
|
|
7183
|
-
const snapshotPath =
|
|
7287
|
+
const snapshotPath = path31.join(root, READINESS_SNAPSHOT_RELATIVE_PATH);
|
|
7184
7288
|
let data;
|
|
7185
7289
|
try {
|
|
7186
|
-
data = JSON.parse(await
|
|
7290
|
+
data = JSON.parse(await readFile19(snapshotPath, "utf8"));
|
|
7187
7291
|
} catch {
|
|
7188
7292
|
return null;
|
|
7189
7293
|
}
|
|
@@ -7214,13 +7318,13 @@ Optional readiness item: \`${first.id}\`. ${first.recommendation} This does not
|
|
|
7214
7318
|
}
|
|
7215
7319
|
async function dogfoodInboxSection(root) {
|
|
7216
7320
|
const candidateReadmes = [
|
|
7217
|
-
|
|
7218
|
-
|
|
7321
|
+
path31.join(root, "dogfood", "README.md"),
|
|
7322
|
+
path31.join(root, ".cursor", "dogfood", "README.md")
|
|
7219
7323
|
];
|
|
7220
7324
|
for (const readme of candidateReadmes) {
|
|
7221
|
-
if (!await
|
|
7325
|
+
if (!await fileExists(readme)) continue;
|
|
7222
7326
|
try {
|
|
7223
|
-
const text = await
|
|
7327
|
+
const text = await readFile19(readme, "utf8");
|
|
7224
7328
|
if (parseUnprocessedDogfoodItems(text).length) return DOGFOOD_INBOX_HINT;
|
|
7225
7329
|
} catch {
|
|
7226
7330
|
}
|
|
@@ -7228,18 +7332,13 @@ async function dogfoodInboxSection(root) {
|
|
|
7228
7332
|
return null;
|
|
7229
7333
|
}
|
|
7230
7334
|
async function loadUpdateCheckPrefs(root) {
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
const uc = data.updateCheck;
|
|
7236
|
-
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
7237
|
-
return null;
|
|
7238
|
-
}
|
|
7239
|
-
return uc;
|
|
7240
|
-
} catch {
|
|
7335
|
+
const data = await loadContextConfig(root);
|
|
7336
|
+
if (!data) return null;
|
|
7337
|
+
const uc = data.updateCheck;
|
|
7338
|
+
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
7241
7339
|
return null;
|
|
7242
7340
|
}
|
|
7341
|
+
return uc;
|
|
7243
7342
|
}
|
|
7244
7343
|
function runUpdateCheckJson(root) {
|
|
7245
7344
|
return new Promise((resolve3) => {
|
|
@@ -7306,18 +7405,13 @@ async function updateCheckSection(root) {
|
|
|
7306
7405
|
return UPDATE_CHECK_NUDGE.replace("{installed}", installed).replace("{latest}", latest);
|
|
7307
7406
|
}
|
|
7308
7407
|
async function loadCursorUpdateCheckPrefs(root) {
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
const uc = data.cursorUpdateCheck;
|
|
7314
|
-
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
7315
|
-
return null;
|
|
7316
|
-
}
|
|
7317
|
-
return uc;
|
|
7318
|
-
} catch {
|
|
7408
|
+
const data = await loadContextConfig(root);
|
|
7409
|
+
if (!data) return null;
|
|
7410
|
+
const uc = data.cursorUpdateCheck;
|
|
7411
|
+
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
7319
7412
|
return null;
|
|
7320
7413
|
}
|
|
7414
|
+
return uc;
|
|
7321
7415
|
}
|
|
7322
7416
|
function runCursorAwarenessJson(root) {
|
|
7323
7417
|
return new Promise((resolve3) => {
|
|
@@ -7452,7 +7546,7 @@ function parseScreenDetachedAuditSessions(listing) {
|
|
|
7452
7546
|
const [, pid, name, marker] = m;
|
|
7453
7547
|
if (!name || !name.startsWith(AUDIT_SESSION_NS_PREFIX)) continue;
|
|
7454
7548
|
if (marker && /[Aa]ttached/.test(marker)) continue;
|
|
7455
|
-
entries.push({ name, socketPath: sockdir ?
|
|
7549
|
+
entries.push({ name, socketPath: sockdir ? path31.join(sockdir, `${pid}.${name}`) : null });
|
|
7456
7550
|
}
|
|
7457
7551
|
return entries;
|
|
7458
7552
|
}
|
|
@@ -7500,8 +7594,8 @@ ${sessions.length} detached \`agent-kit-audit-*\` PTY ${noun} on this host (${ol
|
|
|
7500
7594
|
}
|
|
7501
7595
|
}
|
|
7502
7596
|
async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
7503
|
-
const root =
|
|
7504
|
-
const handoffPath =
|
|
7597
|
+
const root = path31.resolve(rootDir);
|
|
7598
|
+
const handoffPath = path31.join(root, HANDOFF_REL);
|
|
7505
7599
|
const handoffFull = await readFull(handoffPath);
|
|
7506
7600
|
const handoff = capHandoffExcerpt(handoffFull);
|
|
7507
7601
|
const parts = [HARD_RULES];
|
|
@@ -7568,7 +7662,7 @@ async function runSessionStartHook(cwd, formatArg, deps = {}) {
|
|
|
7568
7662
|
const readStdin = deps.readStdin ?? readStdinJson;
|
|
7569
7663
|
const buildContext = deps.buildContext ?? buildSessionStartAdditionalContext;
|
|
7570
7664
|
const payload = await readStdin();
|
|
7571
|
-
const root = resolveSessionRoot(payload,
|
|
7665
|
+
const root = resolveSessionRoot(payload, path32.resolve(cwd));
|
|
7572
7666
|
const out = await buildContext(root, payload);
|
|
7573
7667
|
return formatSessionStartOutput(out.additional_context, format);
|
|
7574
7668
|
} catch {
|
|
@@ -7621,12 +7715,12 @@ import { defineCommand as defineCommand12 } from "citty";
|
|
|
7621
7715
|
|
|
7622
7716
|
// src/lifecycle/path-cli.ts
|
|
7623
7717
|
import { spawn as spawn6 } from "child_process";
|
|
7624
|
-
import
|
|
7718
|
+
import path34 from "path";
|
|
7625
7719
|
|
|
7626
7720
|
// src/utils/terminal.ts
|
|
7627
7721
|
import { readdir as readdir8 } from "fs/promises";
|
|
7628
7722
|
import { homedir as homedir3 } from "os";
|
|
7629
|
-
import
|
|
7723
|
+
import path33 from "path";
|
|
7630
7724
|
import { confirm, isCancel } from "@clack/prompts";
|
|
7631
7725
|
function isNonInteractive() {
|
|
7632
7726
|
if (process.env.CI === "true" || process.env.CI === "1") return true;
|
|
@@ -7650,14 +7744,14 @@ async function findNestedRepoChildren(resolved) {
|
|
|
7650
7744
|
if (!entry.isDirectory()) continue;
|
|
7651
7745
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
7652
7746
|
scanned += 1;
|
|
7653
|
-
if (await fileExists(
|
|
7747
|
+
if (await fileExists(path33.join(resolved, entry.name, ".git"))) {
|
|
7654
7748
|
nested.push(entry.name);
|
|
7655
7749
|
}
|
|
7656
7750
|
}
|
|
7657
7751
|
return nested;
|
|
7658
7752
|
}
|
|
7659
7753
|
async function validateProjectRoot(resolved) {
|
|
7660
|
-
const home =
|
|
7754
|
+
const home = path33.resolve(homedir3());
|
|
7661
7755
|
if (resolved === "/" || resolved === home) {
|
|
7662
7756
|
return {
|
|
7663
7757
|
ok: false,
|
|
@@ -7665,12 +7759,12 @@ async function validateProjectRoot(resolved) {
|
|
|
7665
7759
|
recovery: "Change into a project directory and re-run. Agent Kit installs per project."
|
|
7666
7760
|
};
|
|
7667
7761
|
}
|
|
7668
|
-
const hasGit = await fileExists(
|
|
7669
|
-
const hasManifest = await fileExists(
|
|
7762
|
+
const hasGit = await fileExists(path33.join(resolved, ".git"));
|
|
7763
|
+
const hasManifest = await fileExists(path33.join(resolved, MANIFEST_RELATIVE_PATH));
|
|
7670
7764
|
if (!hasGit && !hasManifest) {
|
|
7671
7765
|
return {
|
|
7672
7766
|
ok: false,
|
|
7673
|
-
reason: `Refused ${resolved}: no .git and no
|
|
7767
|
+
reason: `Refused ${resolved}: no .git and no ${MANIFEST_RELATIVE_PATH}.`,
|
|
7674
7768
|
recovery: [
|
|
7675
7769
|
"Starting from an empty folder? Pick one of these:",
|
|
7676
7770
|
" 1. git init - then re-run. Recommended: readiness and the",
|
|
@@ -7699,7 +7793,7 @@ async function validateProjectRoot(resolved) {
|
|
|
7699
7793
|
return { ok: true };
|
|
7700
7794
|
}
|
|
7701
7795
|
async function confirmProjectRoot(cwd, opts) {
|
|
7702
|
-
const resolved =
|
|
7796
|
+
const resolved = path33.resolve(cwd);
|
|
7703
7797
|
if (opts.forceRoot) {
|
|
7704
7798
|
return resolved;
|
|
7705
7799
|
}
|
|
@@ -7813,9 +7907,9 @@ function npxPinned(runtimeVersion, subcommand) {
|
|
|
7813
7907
|
return `npx -y ${pinnedCliSpec(runtimeVersion)} ${subcommand}`;
|
|
7814
7908
|
}
|
|
7815
7909
|
function isBinUnderPrefix(binPath, prefix) {
|
|
7816
|
-
const bin =
|
|
7817
|
-
const root =
|
|
7818
|
-
return bin === root || bin.startsWith(`${root}${
|
|
7910
|
+
const bin = path34.resolve(binPath);
|
|
7911
|
+
const root = path34.resolve(prefix);
|
|
7912
|
+
return bin === root || bin.startsWith(`${root}${path34.sep}`);
|
|
7819
7913
|
}
|
|
7820
7914
|
function pathCliStatus(env, runtimeVersion) {
|
|
7821
7915
|
if (!env.binOnPath || !env.binPath) return "missing";
|
|
@@ -7913,17 +8007,17 @@ async function syncPathCliToRuntime(options = {}) {
|
|
|
7913
8007
|
}
|
|
7914
8008
|
|
|
7915
8009
|
// src/commands/install.ts
|
|
7916
|
-
import
|
|
8010
|
+
import path40 from "path";
|
|
7917
8011
|
import { defineCommand as defineCommand11 } from "citty";
|
|
7918
8012
|
import { bold, cyan as cyan3, green as green2, options as koloristOptions2 } from "kolorist";
|
|
7919
8013
|
|
|
7920
8014
|
// src/generator/personalization.ts
|
|
7921
8015
|
import { readFile as readFile21, writeFile as writeFile12 } from "fs/promises";
|
|
7922
|
-
import
|
|
8016
|
+
import path38 from "path";
|
|
7923
8017
|
|
|
7924
8018
|
// src/generator/claude-command-adapters.ts
|
|
7925
|
-
import { readFile as
|
|
7926
|
-
import
|
|
8019
|
+
import { readFile as readFile20, readdir as readdir9, writeFile as writeFile9 } from "fs/promises";
|
|
8020
|
+
import path35 from "path";
|
|
7927
8021
|
var CURSOR_COMMANDS_DIR_REL = ".cursor/commands";
|
|
7928
8022
|
var CLAUDE_COMMANDS_DIR_REL = ".claude/commands";
|
|
7929
8023
|
var RESERVED_ADAPTER_NAMES = /* @__PURE__ */ new Set(["agent-kit"]);
|
|
@@ -7952,7 +8046,7 @@ Adapter rules (Claude Code CLI):
|
|
|
7952
8046
|
`;
|
|
7953
8047
|
}
|
|
7954
8048
|
async function discoverInstalledCommands(rootDir) {
|
|
7955
|
-
const dir =
|
|
8049
|
+
const dir = path35.join(rootDir, CURSOR_COMMANDS_DIR_REL);
|
|
7956
8050
|
let entries;
|
|
7957
8051
|
try {
|
|
7958
8052
|
entries = (await readdir9(dir)).filter((f) => f.endsWith(".md"));
|
|
@@ -7964,7 +8058,7 @@ async function discoverInstalledCommands(rootDir) {
|
|
|
7964
8058
|
const name = file.slice(0, -3);
|
|
7965
8059
|
if (RESERVED_ADAPTER_NAMES.has(name)) continue;
|
|
7966
8060
|
try {
|
|
7967
|
-
const raw = await
|
|
8061
|
+
const raw = await readFile20(path35.join(dir, file), "utf8");
|
|
7968
8062
|
const parsed = parseCommandFrontmatter(name, raw);
|
|
7969
8063
|
if (parsed) commands.push(parsed);
|
|
7970
8064
|
} catch {
|
|
@@ -7979,18 +8073,18 @@ async function generateClaudeCommandAdapters(rootDir) {
|
|
|
7979
8073
|
const results = [];
|
|
7980
8074
|
let ledgerDirty = false;
|
|
7981
8075
|
for (const command of commands) {
|
|
7982
|
-
const relPath =
|
|
8076
|
+
const relPath = path35.posix.join(CLAUDE_COMMANDS_DIR_REL, `${command.name}.md`);
|
|
7983
8077
|
const rendered = renderClaudeCommandAdapter(command);
|
|
7984
|
-
const abs =
|
|
8078
|
+
const abs = path35.join(rootDir, relPath);
|
|
7985
8079
|
if (!await fileExists(abs)) {
|
|
7986
|
-
await ensureDir(
|
|
7987
|
-
await
|
|
8080
|
+
await ensureDir(path35.dirname(abs));
|
|
8081
|
+
await writeFile9(abs, rendered, "utf8");
|
|
7988
8082
|
ledger.hashes[relPath] = contentHash(rendered);
|
|
7989
8083
|
ledgerDirty = true;
|
|
7990
8084
|
results.push({ relativePath: relPath, status: "applied" });
|
|
7991
8085
|
continue;
|
|
7992
8086
|
}
|
|
7993
|
-
const localContent = await
|
|
8087
|
+
const localContent = await readFile20(abs, "utf8");
|
|
7994
8088
|
if (localContent === rendered) {
|
|
7995
8089
|
if (ledger.hashes[relPath] !== contentHash(rendered)) {
|
|
7996
8090
|
ledger.hashes[relPath] = contentHash(rendered);
|
|
@@ -8003,7 +8097,7 @@ async function generateClaudeCommandAdapters(rootDir) {
|
|
|
8003
8097
|
results.push({ relativePath: relPath, status: "preserved-customized" });
|
|
8004
8098
|
continue;
|
|
8005
8099
|
}
|
|
8006
|
-
await
|
|
8100
|
+
await writeFile9(abs, rendered, "utf8");
|
|
8007
8101
|
ledger.hashes[relPath] = contentHash(rendered);
|
|
8008
8102
|
ledgerDirty = true;
|
|
8009
8103
|
results.push({ relativePath: relPath, status: "refreshed" });
|
|
@@ -8013,8 +8107,8 @@ async function generateClaudeCommandAdapters(rootDir) {
|
|
|
8013
8107
|
}
|
|
8014
8108
|
|
|
8015
8109
|
// src/generator/claude-kit-load.ts
|
|
8016
|
-
import { writeFile as
|
|
8017
|
-
import
|
|
8110
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
8111
|
+
import path36 from "path";
|
|
8018
8112
|
var CLAUDE_MD_REL = "CLAUDE.md";
|
|
8019
8113
|
var AGENT_KIT_COMMAND_REL = ".claude/commands/agent-kit.md";
|
|
8020
8114
|
function renderClaudeMd() {
|
|
@@ -8070,12 +8164,12 @@ Non-goals: not audits / \`/plan-external-review\`, not a second tick dialect (\`
|
|
|
8070
8164
|
`;
|
|
8071
8165
|
}
|
|
8072
8166
|
async function writeUnlessExists(rootDir, relativePath, content) {
|
|
8073
|
-
const target =
|
|
8167
|
+
const target = path36.join(rootDir, relativePath);
|
|
8074
8168
|
if (await fileExists(target)) {
|
|
8075
8169
|
return { relativePath, status: "skipped-customized" };
|
|
8076
8170
|
}
|
|
8077
|
-
await ensureDir(
|
|
8078
|
-
await
|
|
8171
|
+
await ensureDir(path36.dirname(target));
|
|
8172
|
+
await writeFile10(target, content, "utf8");
|
|
8079
8173
|
return { relativePath, status: "applied" };
|
|
8080
8174
|
}
|
|
8081
8175
|
async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
@@ -8085,109 +8179,9 @@ async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
|
8085
8179
|
]);
|
|
8086
8180
|
}
|
|
8087
8181
|
|
|
8088
|
-
// src/generator/claude-session-start-hook.ts
|
|
8089
|
-
import { readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
|
|
8090
|
-
import path35 from "path";
|
|
8091
|
-
var CLAUDE_SETTINGS_REL = ".claude/settings.json";
|
|
8092
|
-
var RESOLVE_AGENT_KIT_REL = ".cursor/hooks/agent/resolve-agent-kit.sh";
|
|
8093
|
-
var SESSION_START_HOOK_MARKER = "hook session-start --format claude";
|
|
8094
|
-
var SESSION_START_DEGRADED_TEXT = "Agent Kit hooks are running in degraded fail-open mode: the agent-kit CLI could not be resolved (checked AGENT_KIT_HOOK_BIN, PATH, node_modules/.bin/agent-kit). Slash command adapters still work; session-context injection is inactive. Fix: install the CLI (npm i -D @dadado/agent-kit-cli) or set AGENT_KIT_HOOK_BIN.";
|
|
8095
|
-
function buildSessionStartHookCommand() {
|
|
8096
|
-
return `. "\${CLAUDE_PROJECT_DIR}/${RESOLVE_AGENT_KIT_REL}" 2>/dev/null && resolve_agent_kit && exec $AGENT_KIT_RESOLVED hook session-start --format claude; printf '%s' ${shellSingleQuote(SESSION_START_DEGRADED_TEXT)}`;
|
|
8097
|
-
}
|
|
8098
|
-
function shellSingleQuote(text) {
|
|
8099
|
-
return `'${text.replace(/'/g, "'\\''")}'`;
|
|
8100
|
-
}
|
|
8101
|
-
function buildSessionStartHookEntry() {
|
|
8102
|
-
return {
|
|
8103
|
-
type: "command",
|
|
8104
|
-
command: buildSessionStartHookCommand(),
|
|
8105
|
-
timeout: 15,
|
|
8106
|
-
statusMessage: "Loading Agent Kit session context"
|
|
8107
|
-
};
|
|
8108
|
-
}
|
|
8109
|
-
function isPlainObject2(value) {
|
|
8110
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8111
|
-
}
|
|
8112
|
-
function groupHasMarker(group) {
|
|
8113
|
-
if (!isPlainObject2(group) || !Array.isArray(group.hooks)) return false;
|
|
8114
|
-
return group.hooks.some(
|
|
8115
|
-
(h) => isPlainObject2(h) && typeof h.command === "string" && h.command.includes(SESSION_START_HOOK_MARKER)
|
|
8116
|
-
);
|
|
8117
|
-
}
|
|
8118
|
-
function mergeSessionStartHookIntoSettings(existingRaw) {
|
|
8119
|
-
const entry = buildSessionStartHookEntry();
|
|
8120
|
-
const newGroup = { hooks: [entry] };
|
|
8121
|
-
let root = {};
|
|
8122
|
-
if (existingRaw !== null && existingRaw.trim() !== "") {
|
|
8123
|
-
try {
|
|
8124
|
-
const parsed = JSON.parse(existingRaw);
|
|
8125
|
-
if (!isPlainObject2(parsed)) throw new Error("root is not an object");
|
|
8126
|
-
root = parsed;
|
|
8127
|
-
} catch {
|
|
8128
|
-
return {
|
|
8129
|
-
content: null,
|
|
8130
|
-
status: "unavailable",
|
|
8131
|
-
instructions: instructionsBlock(entry)
|
|
8132
|
-
};
|
|
8133
|
-
}
|
|
8134
|
-
}
|
|
8135
|
-
const hooks = isPlainObject2(root.hooks) ? { ...root.hooks } : {};
|
|
8136
|
-
const sessionStart = Array.isArray(hooks.SessionStart) ? [...hooks.SessionStart] : [];
|
|
8137
|
-
const existingIndex = sessionStart.findIndex((g) => groupHasMarker(g));
|
|
8138
|
-
let status;
|
|
8139
|
-
if (existingIndex === -1) {
|
|
8140
|
-
sessionStart.push(newGroup);
|
|
8141
|
-
status = "applied";
|
|
8142
|
-
} else {
|
|
8143
|
-
const current = sessionStart[existingIndex];
|
|
8144
|
-
if (JSON.stringify(current) === JSON.stringify(newGroup)) {
|
|
8145
|
-
status = "unchanged";
|
|
8146
|
-
} else {
|
|
8147
|
-
sessionStart[existingIndex] = newGroup;
|
|
8148
|
-
status = "refreshed";
|
|
8149
|
-
}
|
|
8150
|
-
}
|
|
8151
|
-
hooks.SessionStart = sessionStart;
|
|
8152
|
-
root.hooks = hooks;
|
|
8153
|
-
return { content: `${JSON.stringify(root, null, 2)}
|
|
8154
|
-
`, status };
|
|
8155
|
-
}
|
|
8156
|
-
function instructionsBlock(entry) {
|
|
8157
|
-
return [
|
|
8158
|
-
`Could not parse the existing ${CLAUDE_SETTINGS_REL} as JSON, so Agent Kit did not touch it.`,
|
|
8159
|
-
"Add this hook by hand under hooks.SessionStart (create the arrays if they do not exist):",
|
|
8160
|
-
"",
|
|
8161
|
-
JSON.stringify(entry, null, 2)
|
|
8162
|
-
].join("\n");
|
|
8163
|
-
}
|
|
8164
|
-
async function writeClaudeSessionStartHook(rootDir) {
|
|
8165
|
-
const abs = path35.join(rootDir, CLAUDE_SETTINGS_REL);
|
|
8166
|
-
let existing = null;
|
|
8167
|
-
try {
|
|
8168
|
-
existing = await readFile20(abs, "utf8");
|
|
8169
|
-
} catch {
|
|
8170
|
-
existing = null;
|
|
8171
|
-
}
|
|
8172
|
-
const merged = mergeSessionStartHookIntoSettings(existing);
|
|
8173
|
-
if (merged.status === "unavailable" || merged.content === null) {
|
|
8174
|
-
return {
|
|
8175
|
-
relativePath: CLAUDE_SETTINGS_REL,
|
|
8176
|
-
status: "unavailable",
|
|
8177
|
-
instructions: merged.instructions
|
|
8178
|
-
};
|
|
8179
|
-
}
|
|
8180
|
-
if (merged.status === "unchanged") {
|
|
8181
|
-
return { relativePath: CLAUDE_SETTINGS_REL, status: "unchanged" };
|
|
8182
|
-
}
|
|
8183
|
-
await ensureDir(path35.dirname(abs));
|
|
8184
|
-
await writeFile10(abs, merged.content, "utf8");
|
|
8185
|
-
return { relativePath: CLAUDE_SETTINGS_REL, status: merged.status };
|
|
8186
|
-
}
|
|
8187
|
-
|
|
8188
8182
|
// src/generator/vscode.ts
|
|
8189
8183
|
import { writeFile as writeFile11 } from "fs/promises";
|
|
8190
|
-
import
|
|
8184
|
+
import path37 from "path";
|
|
8191
8185
|
|
|
8192
8186
|
// src/generator/platform.ts
|
|
8193
8187
|
function gitProviderLabel(profile) {
|
|
@@ -8211,10 +8205,10 @@ function prTerminology(profile) {
|
|
|
8211
8205
|
// src/generator/vscode.ts
|
|
8212
8206
|
async function generateVSCodeArtifacts(profile) {
|
|
8213
8207
|
const results = [];
|
|
8214
|
-
const vscodeDir =
|
|
8215
|
-
const githubDir =
|
|
8208
|
+
const vscodeDir = path37.join(profile.rootDir, ".vscode");
|
|
8209
|
+
const githubDir = path37.join(profile.rootDir, ".github");
|
|
8216
8210
|
await Promise.all([ensureDir(vscodeDir), ensureDir(githubDir)]);
|
|
8217
|
-
const settingsPath =
|
|
8211
|
+
const settingsPath = path37.join(vscodeDir, "settings.json");
|
|
8218
8212
|
if (await fileExists(settingsPath)) {
|
|
8219
8213
|
results.push({ relativePath: ".vscode/settings.json", status: "skipped-customized" });
|
|
8220
8214
|
} else {
|
|
@@ -8238,7 +8232,7 @@ async function generateVSCodeArtifacts(profile) {
|
|
|
8238
8232
|
}
|
|
8239
8233
|
const provider = gitProviderLabel(profile);
|
|
8240
8234
|
const prTerm = prTerminology(profile);
|
|
8241
|
-
const copilotPath =
|
|
8235
|
+
const copilotPath = path37.join(githubDir, "copilot-instructions.md");
|
|
8242
8236
|
if (await fileExists(copilotPath)) {
|
|
8243
8237
|
results.push({ relativePath: ".github/copilot-instructions.md", status: "skipped-customized" });
|
|
8244
8238
|
} else {
|
|
@@ -8256,7 +8250,7 @@ async function generateVSCodeArtifacts(profile) {
|
|
|
8256
8250
|
results.push({ relativePath: ".github/copilot-instructions.md", status: "applied" });
|
|
8257
8251
|
}
|
|
8258
8252
|
if (profile.ide.plan === "vscode-pro") {
|
|
8259
|
-
const securityPath =
|
|
8253
|
+
const securityPath = path37.join(vscodeDir, "security-review.agent.md");
|
|
8260
8254
|
if (await fileExists(securityPath)) {
|
|
8261
8255
|
results.push({
|
|
8262
8256
|
relativePath: ".vscode/security-review.agent.md",
|
|
@@ -8468,7 +8462,7 @@ function renderProjectContext(profile, skillItems = []) {
|
|
|
8468
8462
|
`;
|
|
8469
8463
|
}
|
|
8470
8464
|
async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
8471
|
-
const target =
|
|
8465
|
+
const target = path38.join(rootDir, relativePath);
|
|
8472
8466
|
if (await fileExists(target)) {
|
|
8473
8467
|
return {
|
|
8474
8468
|
kind: "file",
|
|
@@ -8478,7 +8472,7 @@ async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
|
8478
8472
|
evidence
|
|
8479
8473
|
};
|
|
8480
8474
|
}
|
|
8481
|
-
await ensureDir(
|
|
8475
|
+
await ensureDir(path38.dirname(target));
|
|
8482
8476
|
await writeFile12(target, content, "utf8");
|
|
8483
8477
|
return {
|
|
8484
8478
|
kind: "file",
|
|
@@ -8495,7 +8489,7 @@ async function packTargets(registryRoot, packId) {
|
|
|
8495
8489
|
async function existingTargets(projectRoot, targets) {
|
|
8496
8490
|
const checks = await Promise.all(
|
|
8497
8491
|
targets.map(
|
|
8498
|
-
async (target) => await fileExists(
|
|
8492
|
+
async (target) => await fileExists(path38.join(projectRoot, target)) ? target : null
|
|
8499
8493
|
)
|
|
8500
8494
|
);
|
|
8501
8495
|
return checks.filter((target) => target !== null);
|
|
@@ -8517,14 +8511,14 @@ async function applyPersonalization(input) {
|
|
|
8517
8511
|
componentResults.push({ ...item, status: "unavailable" });
|
|
8518
8512
|
continue;
|
|
8519
8513
|
}
|
|
8520
|
-
const target =
|
|
8514
|
+
const target = path38.posix.join(
|
|
8521
8515
|
".cursor",
|
|
8522
8516
|
"skills",
|
|
8523
8517
|
skill.path.includes("/core/") ? "core" : "community",
|
|
8524
8518
|
skill.id,
|
|
8525
8519
|
"SKILL.md"
|
|
8526
8520
|
);
|
|
8527
|
-
if (await fileExists(
|
|
8521
|
+
if (await fileExists(path38.join(input.rootDir, target))) {
|
|
8528
8522
|
componentResults.push({ ...item, status: "skipped-customized", path: target });
|
|
8529
8523
|
protectedPaths.add(target);
|
|
8530
8524
|
continue;
|
|
@@ -8669,7 +8663,7 @@ async function applyPersonalization(input) {
|
|
|
8669
8663
|
protectedPaths: [...protectedPaths].sort(),
|
|
8670
8664
|
...claudeSessionStartInstructions ? { claudeSessionStartInstructions } : {}
|
|
8671
8665
|
};
|
|
8672
|
-
await writeJson(
|
|
8666
|
+
await writeJson(path38.join(input.rootDir, RESULT_PATH), result);
|
|
8673
8667
|
return {
|
|
8674
8668
|
result,
|
|
8675
8669
|
manifest: {
|
|
@@ -8687,7 +8681,7 @@ async function applyPersonalization(input) {
|
|
|
8687
8681
|
};
|
|
8688
8682
|
}
|
|
8689
8683
|
async function readRepositoryProfile(rootDir) {
|
|
8690
|
-
const target =
|
|
8684
|
+
const target = path38.join(rootDir, REPOSITORY_PROFILE_REL);
|
|
8691
8685
|
if (!await fileExists(target)) return null;
|
|
8692
8686
|
return JSON.parse(await readFile21(target, "utf8"));
|
|
8693
8687
|
}
|
|
@@ -8695,16 +8689,16 @@ async function readRepositoryProfile(rootDir) {
|
|
|
8695
8689
|
// src/lifecycle/onboard-migration.ts
|
|
8696
8690
|
import { createHash as createHash4 } from "crypto";
|
|
8697
8691
|
import { readFile as readFile22, unlink } from "fs/promises";
|
|
8698
|
-
import
|
|
8692
|
+
import path39 from "path";
|
|
8699
8693
|
var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
|
|
8700
8694
|
var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
|
|
8701
8695
|
var MANAGED_LEGACY_HASHES = /* @__PURE__ */ new Set([
|
|
8702
8696
|
"b274a68941813f19b185893cb7c5561dff027f53270890029992f208e24992fe"
|
|
8703
8697
|
]);
|
|
8704
8698
|
async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_LEGACY_HASHES) {
|
|
8705
|
-
const legacyPath =
|
|
8699
|
+
const legacyPath = path39.join(projectRoot, LEGACY_ONBOARD_PATH);
|
|
8706
8700
|
if (!await fileExists(legacyPath)) return "absent";
|
|
8707
|
-
const namespacedPath =
|
|
8701
|
+
const namespacedPath = path39.join(projectRoot, NAMESPACED_ONBOARD_PATH);
|
|
8708
8702
|
if (!await fileExists(namespacedPath)) return "preserved-customized";
|
|
8709
8703
|
const content = await readFile22(legacyPath);
|
|
8710
8704
|
const hash = createHash4("sha256").update(content).digest("hex");
|
|
@@ -8756,6 +8750,21 @@ async function syncFromManifest(registryRoot, projectRoot, manifest) {
|
|
|
8756
8750
|
return stats;
|
|
8757
8751
|
}
|
|
8758
8752
|
|
|
8753
|
+
// src/commands/shared-args.ts
|
|
8754
|
+
var NON_INTERACTIVE_ROOT_ARGS = {
|
|
8755
|
+
yes: {
|
|
8756
|
+
type: "boolean",
|
|
8757
|
+
alias: "y",
|
|
8758
|
+
description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
|
|
8759
|
+
default: false
|
|
8760
|
+
},
|
|
8761
|
+
"force-root": {
|
|
8762
|
+
type: "boolean",
|
|
8763
|
+
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
8764
|
+
default: false
|
|
8765
|
+
}
|
|
8766
|
+
};
|
|
8767
|
+
|
|
8759
8768
|
// src/commands/install.ts
|
|
8760
8769
|
function parsePackList(raw) {
|
|
8761
8770
|
if (!raw?.trim()) return [];
|
|
@@ -8840,7 +8849,7 @@ async function printPostInstallSummary(result) {
|
|
|
8840
8849
|
printInstallEpilogue(sync.env, { runtimeVersion: KIT_VERSION });
|
|
8841
8850
|
}
|
|
8842
8851
|
async function performInstall(options) {
|
|
8843
|
-
const projectRoot =
|
|
8852
|
+
const projectRoot = path40.resolve(options.cwd);
|
|
8844
8853
|
const packs = parsePackList(options.pack);
|
|
8845
8854
|
const existing = await loadAgentKitManifest(projectRoot);
|
|
8846
8855
|
const registry = await resolveRegistryFromCli({
|
|
@@ -8917,17 +8926,7 @@ var installCommand = defineCommand11({
|
|
|
8917
8926
|
type: "string",
|
|
8918
8927
|
description: `Comma-separated L1 pack ids (e.g. cybersec,devops). Known: ${DOMAIN_PACK_IDS.join(", ")}`
|
|
8919
8928
|
},
|
|
8920
|
-
|
|
8921
|
-
type: "boolean",
|
|
8922
|
-
alias: "y",
|
|
8923
|
-
description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
|
|
8924
|
-
default: false
|
|
8925
|
-
},
|
|
8926
|
-
"force-root": {
|
|
8927
|
-
type: "boolean",
|
|
8928
|
-
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
8929
|
-
default: false
|
|
8930
|
-
},
|
|
8929
|
+
...NON_INTERACTIVE_ROOT_ARGS,
|
|
8931
8930
|
claude: {
|
|
8932
8931
|
type: "boolean",
|
|
8933
8932
|
description: "Opt-in: generate .claude/commands/*.md thin pointer adapters for the installed .cursor/commands set, and merge a SessionStart context hook into .claude/settings.json (default install behavior is unchanged without this flag)",
|
|
@@ -9022,17 +9021,7 @@ var initCommand = defineCommand12({
|
|
|
9022
9021
|
description: "Project root directory",
|
|
9023
9022
|
default: process.cwd()
|
|
9024
9023
|
},
|
|
9025
|
-
|
|
9026
|
-
type: "boolean",
|
|
9027
|
-
alias: "y",
|
|
9028
|
-
description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
|
|
9029
|
-
default: false
|
|
9030
|
-
},
|
|
9031
|
-
"force-root": {
|
|
9032
|
-
type: "boolean",
|
|
9033
|
-
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
9034
|
-
default: false
|
|
9035
|
-
}
|
|
9024
|
+
...NON_INTERACTIVE_ROOT_ARGS
|
|
9036
9025
|
},
|
|
9037
9026
|
async run({ args }) {
|
|
9038
9027
|
const nonInteractive = args.yes || isNonInteractive();
|
|
@@ -9566,8 +9555,8 @@ function renderMcLive(view, opts = {}) {
|
|
|
9566
9555
|
|
|
9567
9556
|
// src/mission-control/snapshot.ts
|
|
9568
9557
|
import { execFile as execFile6 } from "child_process";
|
|
9569
|
-
import { access as
|
|
9570
|
-
import
|
|
9558
|
+
import { access as access7 } from "fs/promises";
|
|
9559
|
+
import path41 from "path";
|
|
9571
9560
|
|
|
9572
9561
|
// ../../dashboard/lib/live-refresh.mjs
|
|
9573
9562
|
import { join, resolve as resolve2 } from "path";
|
|
@@ -9599,8 +9588,8 @@ function dataScriptTimeoutMs(env = process.env) {
|
|
|
9599
9588
|
async function firstExisting2(candidates) {
|
|
9600
9589
|
for (const candidate2 of candidates) {
|
|
9601
9590
|
try {
|
|
9602
|
-
await
|
|
9603
|
-
return
|
|
9591
|
+
await access7(candidate2);
|
|
9592
|
+
return path41.resolve(candidate2);
|
|
9604
9593
|
} catch {
|
|
9605
9594
|
}
|
|
9606
9595
|
}
|
|
@@ -9609,9 +9598,9 @@ async function firstExisting2(candidates) {
|
|
|
9609
9598
|
async function findDashboardDataScript(cwd, env = process.env, options = {}) {
|
|
9610
9599
|
const startPath = await findDashboardStart(cwd, env, options);
|
|
9611
9600
|
if (startPath) {
|
|
9612
|
-
const sibling =
|
|
9601
|
+
const sibling = path41.join(path41.dirname(startPath), "dashboard-data.mjs");
|
|
9613
9602
|
try {
|
|
9614
|
-
await
|
|
9603
|
+
await access7(sibling);
|
|
9615
9604
|
return sibling;
|
|
9616
9605
|
} catch {
|
|
9617
9606
|
}
|
|
@@ -9624,7 +9613,7 @@ async function loadDashboardSnapshot(opts) {
|
|
|
9624
9613
|
const env = opts.env ?? process.env;
|
|
9625
9614
|
const timeout = dataScriptTimeoutMs(env);
|
|
9626
9615
|
const run = opts.execFileFn ?? execFile6;
|
|
9627
|
-
const snapshotRoot =
|
|
9616
|
+
const snapshotRoot = path41.resolve(opts.snapshotRoot);
|
|
9628
9617
|
return new Promise((resolve3) => {
|
|
9629
9618
|
run(
|
|
9630
9619
|
process.execPath,
|
|
@@ -9947,13 +9936,13 @@ var missionControlCommand = defineCommand13({
|
|
|
9947
9936
|
});
|
|
9948
9937
|
|
|
9949
9938
|
// src/commands/monitors.ts
|
|
9950
|
-
import
|
|
9939
|
+
import path43 from "path";
|
|
9951
9940
|
import { defineCommand as defineCommand14 } from "citty";
|
|
9952
9941
|
|
|
9953
9942
|
// src/invariants/monitors-untriaged.ts
|
|
9954
9943
|
import { execFile as execFile7 } from "child_process";
|
|
9955
9944
|
import { readFile as readFile23, readdir as readdir10, stat as stat5 } from "fs/promises";
|
|
9956
|
-
import
|
|
9945
|
+
import path42 from "path";
|
|
9957
9946
|
import { promisify as promisify6 } from "util";
|
|
9958
9947
|
|
|
9959
9948
|
// src/invariants/triage-heading.ts
|
|
@@ -9994,7 +9983,7 @@ async function gitFreshMonitorNames(rootDir) {
|
|
|
9994
9983
|
for (const line of stdout.split("\n")) {
|
|
9995
9984
|
if (!line.trim()) continue;
|
|
9996
9985
|
const file = line.slice(3).trim().replace(/^.* -> /, "");
|
|
9997
|
-
const base =
|
|
9986
|
+
const base = path42.basename(file);
|
|
9998
9987
|
if (base.startsWith("plan-monitor-") && base.endsWith(".md")) {
|
|
9999
9988
|
names.add(base);
|
|
10000
9989
|
}
|
|
@@ -10017,13 +10006,13 @@ function monitorSlugFromName(fileName) {
|
|
|
10017
10006
|
return fileName.replace(/^plan-monitor-/, "").replace(/\.md$/, "").toLowerCase();
|
|
10018
10007
|
}
|
|
10019
10008
|
async function selectUntriagedMonitors(rootDir) {
|
|
10020
|
-
const root =
|
|
10021
|
-
const memoryDir =
|
|
10009
|
+
const root = path42.resolve(rootDir);
|
|
10010
|
+
const memoryDir = path42.join(root, ".cursor", "memory");
|
|
10022
10011
|
const allNames = await listMonitorFiles(memoryDir);
|
|
10023
10012
|
const selectionOrder = ["git-fresh", "handoff-aligned", "untriaged-scan"];
|
|
10024
10013
|
const byName = /* @__PURE__ */ new Map();
|
|
10025
10014
|
for (const name of allNames) {
|
|
10026
|
-
const abs =
|
|
10015
|
+
const abs = path42.join(memoryDir, name);
|
|
10027
10016
|
try {
|
|
10028
10017
|
const [content, st] = await Promise.all([readFile23(abs, "utf8"), stat5(abs)]);
|
|
10029
10018
|
byName.set(name, { content, mtimeMs: st.mtimeMs });
|
|
@@ -10038,7 +10027,7 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
10038
10027
|
const gitFreshSet = [...gitFresh].filter(untriaged).sort();
|
|
10039
10028
|
let handoff = "";
|
|
10040
10029
|
try {
|
|
10041
|
-
handoff = await readFile23(
|
|
10030
|
+
handoff = await readFile23(path42.join(root, ".cursor", "HANDOFF.md"), "utf8");
|
|
10042
10031
|
} catch {
|
|
10043
10032
|
handoff = "";
|
|
10044
10033
|
}
|
|
@@ -10058,8 +10047,8 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
10058
10047
|
const row = byName.get(name);
|
|
10059
10048
|
if (!row) continue;
|
|
10060
10049
|
entries.push({
|
|
10061
|
-
path:
|
|
10062
|
-
relativePath:
|
|
10050
|
+
path: path42.join(memoryDir, name),
|
|
10051
|
+
relativePath: path42.relative(root, path42.join(memoryDir, name)).split(path42.sep).join("/"),
|
|
10063
10052
|
mtimeMs: row.mtimeMs,
|
|
10064
10053
|
hasTriageHeading: false,
|
|
10065
10054
|
hasOpenGaps: hasOpenGaps(row.content),
|
|
@@ -10105,7 +10094,7 @@ var monitorsCommand = defineCommand14({
|
|
|
10105
10094
|
process.exitCode = 2;
|
|
10106
10095
|
return;
|
|
10107
10096
|
}
|
|
10108
|
-
const result = await selectUntriagedMonitors(
|
|
10097
|
+
const result = await selectUntriagedMonitors(path43.resolve(args.cwd));
|
|
10109
10098
|
if (args.json) {
|
|
10110
10099
|
console.log(JSON.stringify(result, null, 2));
|
|
10111
10100
|
return;
|
|
@@ -10121,7 +10110,7 @@ var monitorsCommand = defineCommand14({
|
|
|
10121
10110
|
});
|
|
10122
10111
|
|
|
10123
10112
|
// src/commands/plan-index.ts
|
|
10124
|
-
import
|
|
10113
|
+
import path44 from "path";
|
|
10125
10114
|
import { defineCommand as defineCommand15 } from "citty";
|
|
10126
10115
|
var planIndexCommand = defineCommand15({
|
|
10127
10116
|
meta: {
|
|
@@ -10140,7 +10129,7 @@ var planIndexCommand = defineCommand15({
|
|
|
10140
10129
|
}
|
|
10141
10130
|
},
|
|
10142
10131
|
async run({ args }) {
|
|
10143
|
-
const root =
|
|
10132
|
+
const root = path44.resolve(args.cwd);
|
|
10144
10133
|
const index = await writePlanIndex(root);
|
|
10145
10134
|
if (args.json) {
|
|
10146
10135
|
console.log(JSON.stringify(index, null, 2));
|
|
@@ -10156,14 +10145,14 @@ var planIndexCommand = defineCommand15({
|
|
|
10156
10145
|
});
|
|
10157
10146
|
|
|
10158
10147
|
// src/commands/run-plan.ts
|
|
10159
|
-
import
|
|
10148
|
+
import path51 from "path";
|
|
10160
10149
|
import { defineCommand as defineCommand16 } from "citty";
|
|
10161
10150
|
|
|
10162
10151
|
// src/plan-loop/backends.ts
|
|
10163
10152
|
import { execFileSync as execFileSync2, spawn as spawn7 } from "child_process";
|
|
10164
10153
|
import { createWriteStream } from "fs";
|
|
10165
|
-
import { access as
|
|
10166
|
-
import
|
|
10154
|
+
import { access as access8 } from "fs/promises";
|
|
10155
|
+
import path45 from "path";
|
|
10167
10156
|
import { StringDecoder as StringDecoder3 } from "string_decoder";
|
|
10168
10157
|
|
|
10169
10158
|
// src/plan-loop/hitl-relay.ts
|
|
@@ -11191,26 +11180,32 @@ function createRelay(hitl, child, redactedError) {
|
|
|
11191
11180
|
}
|
|
11192
11181
|
};
|
|
11193
11182
|
}
|
|
11183
|
+
function cursorAgentHeadlessArgs(opts) {
|
|
11184
|
+
const args = [
|
|
11185
|
+
"-p",
|
|
11186
|
+
"--force",
|
|
11187
|
+
"--sandbox",
|
|
11188
|
+
"disabled",
|
|
11189
|
+
"--output-format",
|
|
11190
|
+
"stream-json",
|
|
11191
|
+
"--workspace",
|
|
11192
|
+
opts.workspace
|
|
11193
|
+
];
|
|
11194
|
+
if (opts.model) args.push("--model", opts.model);
|
|
11195
|
+
args.push(opts.prompt);
|
|
11196
|
+
return args;
|
|
11197
|
+
}
|
|
11194
11198
|
var cursorAgentBackend = {
|
|
11195
11199
|
id: "cursor-agent",
|
|
11196
11200
|
async resolve() {
|
|
11197
11201
|
return whichBinary("cursor-agent");
|
|
11198
11202
|
},
|
|
11199
11203
|
async run(opts) {
|
|
11200
|
-
const args =
|
|
11201
|
-
|
|
11202
|
-
|
|
11203
|
-
|
|
11204
|
-
|
|
11205
|
-
"--output-format",
|
|
11206
|
-
"stream-json",
|
|
11207
|
-
"--workspace",
|
|
11208
|
-
opts.workspace
|
|
11209
|
-
];
|
|
11210
|
-
if (opts.model) {
|
|
11211
|
-
args.push("--model", opts.model);
|
|
11212
|
-
}
|
|
11213
|
-
args.push(opts.prompt);
|
|
11204
|
+
const args = cursorAgentHeadlessArgs({
|
|
11205
|
+
workspace: opts.workspace,
|
|
11206
|
+
prompt: opts.prompt,
|
|
11207
|
+
model: opts.model
|
|
11208
|
+
});
|
|
11214
11209
|
const env = opts.env ? mergeChildEnv(opts.env) : void 0;
|
|
11215
11210
|
return spawnLogged("cursor-agent", args, opts.logPath, {
|
|
11216
11211
|
spawnFn: opts.spawnFn,
|
|
@@ -11297,9 +11292,9 @@ function claudeHeadlessArgs(opts = {}) {
|
|
|
11297
11292
|
async function missingClaudeAdapter(workspace, prompt) {
|
|
11298
11293
|
const m = /^\/([A-Za-z0-9][A-Za-z0-9_-]*)/.exec(prompt.trimStart());
|
|
11299
11294
|
if (!m?.[1]) return null;
|
|
11300
|
-
const rel =
|
|
11295
|
+
const rel = path45.join(".claude", "commands", `${m[1]}.md`);
|
|
11301
11296
|
try {
|
|
11302
|
-
await
|
|
11297
|
+
await access8(path45.join(workspace, rel));
|
|
11303
11298
|
return null;
|
|
11304
11299
|
} catch {
|
|
11305
11300
|
return rel;
|
|
@@ -11422,15 +11417,15 @@ async function detectAgentBackend(requested, whichFn = whichBinary) {
|
|
|
11422
11417
|
|
|
11423
11418
|
// src/plan-loop/run-loop.ts
|
|
11424
11419
|
import { mkdir as mkdir6, readFile as readFile27, rm as rm2, unlink as unlink2 } from "fs/promises";
|
|
11425
|
-
import
|
|
11420
|
+
import path50 from "path";
|
|
11426
11421
|
|
|
11427
11422
|
// src/mission-control/live-run.ts
|
|
11428
11423
|
import { readFile as readFile25 } from "fs/promises";
|
|
11429
|
-
import
|
|
11424
|
+
import path47 from "path";
|
|
11430
11425
|
|
|
11431
11426
|
// src/plan-loop/plan-state.ts
|
|
11432
11427
|
import { readFile as readFile24, readdir as readdir11 } from "fs/promises";
|
|
11433
|
-
import
|
|
11428
|
+
import path46 from "path";
|
|
11434
11429
|
function countPendingTodos(raw) {
|
|
11435
11430
|
const lines = raw.split(/\r?\n/);
|
|
11436
11431
|
let inFront = 0;
|
|
@@ -11458,7 +11453,7 @@ function countPendingTodos(raw) {
|
|
|
11458
11453
|
async function findActivePlanFile(plansDir) {
|
|
11459
11454
|
if (!await fileExists(plansDir)) return null;
|
|
11460
11455
|
const files = (await readdir11(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
11461
|
-
return files[0] ?
|
|
11456
|
+
return files[0] ? path46.join(plansDir, files[0]) : null;
|
|
11462
11457
|
}
|
|
11463
11458
|
async function readPlan(planPath) {
|
|
11464
11459
|
return readFile24(planPath, "utf8");
|
|
@@ -11646,7 +11641,7 @@ function firstWord(raw) {
|
|
|
11646
11641
|
}
|
|
11647
11642
|
async function readLiveMission(root, io = defaultMissionIo()) {
|
|
11648
11643
|
const mission = emptyLiveMission();
|
|
11649
|
-
const handoff = await io.readFile(
|
|
11644
|
+
const handoff = await io.readFile(path47.join(root, ".cursor", "HANDOFF.md")) ?? "";
|
|
11650
11645
|
const named = extractHandoffNamedPlans(handoff);
|
|
11651
11646
|
mission.mode = firstWord(MODE_RE.exec(handoff)?.[1]);
|
|
11652
11647
|
let cursorIndex = null;
|
|
@@ -11682,13 +11677,13 @@ async function readLiveMission(root, io = defaultMissionIo()) {
|
|
|
11682
11677
|
state: finished || cursor !== null && i < cursor ? "done" : i === cursor ? "current" : "pending"
|
|
11683
11678
|
}));
|
|
11684
11679
|
}
|
|
11685
|
-
const plansDir =
|
|
11680
|
+
const plansDir = path47.join(root, ".cursor", "plans");
|
|
11686
11681
|
const planBase = (mission.queue && !finished ? mission.queue.current : null) ?? named.active;
|
|
11687
|
-
const planPath = planBase ?
|
|
11682
|
+
const planPath = planBase ? path47.join(plansDir, planBase) : await io.findActivePlan(plansDir);
|
|
11688
11683
|
if (planPath) {
|
|
11689
11684
|
const raw = await io.readFile(planPath);
|
|
11690
11685
|
if (raw !== null) {
|
|
11691
|
-
mission.planFile =
|
|
11686
|
+
mission.planFile = path47.basename(planPath);
|
|
11692
11687
|
const { todos } = parsePlanFrontmatter2(raw);
|
|
11693
11688
|
const open = todos.filter((t) => t.status !== "cancelled");
|
|
11694
11689
|
mission.todos = {
|
|
@@ -11739,7 +11734,7 @@ async function withLiveTui(opts, run) {
|
|
|
11739
11734
|
columns,
|
|
11740
11735
|
now: now2,
|
|
11741
11736
|
backend: opts.backend,
|
|
11742
|
-
logPath:
|
|
11737
|
+
logPath: path47.relative(opts.root, opts.logPath) || opts.logPath
|
|
11743
11738
|
});
|
|
11744
11739
|
opts.onFeed?.(feed);
|
|
11745
11740
|
const missionLoader = opts.missionLoader ?? createLiveMissionLoader(opts.root, { now: now2 });
|
|
@@ -11876,9 +11871,9 @@ async function withLiveTui(opts, run) {
|
|
|
11876
11871
|
|
|
11877
11872
|
// src/plan-loop/external-review.ts
|
|
11878
11873
|
import { spawn as spawn8 } from "child_process";
|
|
11879
|
-
import
|
|
11880
|
-
var CANONICAL_REL =
|
|
11881
|
-
var FALLBACK_REL =
|
|
11874
|
+
import path48 from "path";
|
|
11875
|
+
var CANONICAL_REL = path48.join(".cursor", "scripts", "plan-external-review.sh");
|
|
11876
|
+
var FALLBACK_REL = path48.join("scripts", "plan-external-review.sh");
|
|
11882
11877
|
function isPlanExhaustedReason(reason) {
|
|
11883
11878
|
const r = reason.trim().toLowerCase();
|
|
11884
11879
|
if (!r) return false;
|
|
@@ -11900,8 +11895,8 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
11900
11895
|
const existsFn = options.existsFn ?? fileExists;
|
|
11901
11896
|
const log = options.log ?? ((line) => console.log(line));
|
|
11902
11897
|
const force = options.force === true;
|
|
11903
|
-
const canonicalPath =
|
|
11904
|
-
const fallbackPath =
|
|
11898
|
+
const canonicalPath = path48.join(root, CANONICAL_REL);
|
|
11899
|
+
const fallbackPath = path48.join(root, FALLBACK_REL);
|
|
11905
11900
|
let scriptPath = null;
|
|
11906
11901
|
let scriptRel = CANONICAL_REL;
|
|
11907
11902
|
if (await existsFn(canonicalPath)) {
|
|
@@ -11954,7 +11949,7 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
11954
11949
|
}
|
|
11955
11950
|
|
|
11956
11951
|
// src/plan-loop/persona-banners.ts
|
|
11957
|
-
import
|
|
11952
|
+
import path49 from "path";
|
|
11958
11953
|
import {
|
|
11959
11954
|
blue,
|
|
11960
11955
|
cyan as cyan4,
|
|
@@ -11990,7 +11985,7 @@ function resolveColor(name, fallback) {
|
|
|
11990
11985
|
async function resolveCliPersonaId(root) {
|
|
11991
11986
|
try {
|
|
11992
11987
|
const cfg = await readJson(
|
|
11993
|
-
|
|
11988
|
+
path49.join(root, ".cursor", "context", "config.json")
|
|
11994
11989
|
);
|
|
11995
11990
|
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
11996
11991
|
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
@@ -12001,7 +11996,7 @@ async function resolveCliPersonaId(root) {
|
|
|
12001
11996
|
}
|
|
12002
11997
|
async function loadPersonaPack(root, personaId) {
|
|
12003
11998
|
try {
|
|
12004
|
-
const personaPath =
|
|
11999
|
+
const personaPath = path49.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
12005
12000
|
const pack = await readJson(personaPath);
|
|
12006
12001
|
if (!pack || typeof pack.id !== "string") return null;
|
|
12007
12002
|
return pack;
|
|
@@ -12180,9 +12175,9 @@ function sleep(ms) {
|
|
|
12180
12175
|
return new Promise((r) => setTimeout(r, ms));
|
|
12181
12176
|
}
|
|
12182
12177
|
async function runPlanLoop(opts) {
|
|
12183
|
-
const plansDir =
|
|
12184
|
-
const stopFile =
|
|
12185
|
-
const logDir =
|
|
12178
|
+
const plansDir = path50.join(opts.root, ".cursor", "plans");
|
|
12179
|
+
const stopFile = path50.join(opts.root, ".cursor", "loop.stop");
|
|
12180
|
+
const logDir = path50.join(opts.root, ".cursor", "loop-logs");
|
|
12186
12181
|
const planPath = await findActivePlanFile(plansDir);
|
|
12187
12182
|
if (!planPath) {
|
|
12188
12183
|
logger.error("No active plan in .cursor/plans/");
|
|
@@ -12203,7 +12198,7 @@ async function runPlanLoop(opts) {
|
|
|
12203
12198
|
try {
|
|
12204
12199
|
const persona = await loadCliRunPlanPersona(opts.root);
|
|
12205
12200
|
const banners = createPersonaBannerPrinter(persona);
|
|
12206
|
-
console.log(`Active plan: ${
|
|
12201
|
+
console.log(`Active plan: ${path50.basename(planPath)}`);
|
|
12207
12202
|
console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
|
|
12208
12203
|
console.log(`Backend: ${opts.backend.id}`);
|
|
12209
12204
|
if (persona) {
|
|
@@ -12247,8 +12242,8 @@ async function runPlanLoop(opts) {
|
|
|
12247
12242
|
planExhausted = true;
|
|
12248
12243
|
break;
|
|
12249
12244
|
}
|
|
12250
|
-
const logPath =
|
|
12251
|
-
const relLog =
|
|
12245
|
+
const logPath = path50.join(logDir, `tick-${stamp()}.log`);
|
|
12246
|
+
const relLog = path50.relative(opts.root, logPath);
|
|
12252
12247
|
console.log("");
|
|
12253
12248
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
12254
12249
|
if (banners) banners.tickStart(tickLine);
|
|
@@ -12356,7 +12351,7 @@ async function runPlanLoop(opts) {
|
|
|
12356
12351
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
12357
12352
|
if (banners) banners.phaseComplete(finishDetail);
|
|
12358
12353
|
console.log(
|
|
12359
|
-
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${
|
|
12354
|
+
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path50.relative(opts.root, logDir)}/`
|
|
12360
12355
|
);
|
|
12361
12356
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
12362
12357
|
await armExternalPlanReview(opts.root);
|
|
@@ -12448,7 +12443,7 @@ var runPlanCommand = defineCommand16({
|
|
|
12448
12443
|
return;
|
|
12449
12444
|
}
|
|
12450
12445
|
const code = await runPlanLoop({
|
|
12451
|
-
root:
|
|
12446
|
+
root: path51.resolve(args.cwd),
|
|
12452
12447
|
maxTicks,
|
|
12453
12448
|
sleepSeconds,
|
|
12454
12449
|
model: args.model ? String(args.model) : void 0,
|
|
@@ -12463,12 +12458,12 @@ var runPlanCommand = defineCommand16({
|
|
|
12463
12458
|
|
|
12464
12459
|
// src/commands/run.ts
|
|
12465
12460
|
import { homedir as homedir4 } from "os";
|
|
12466
|
-
import
|
|
12461
|
+
import path53 from "path";
|
|
12467
12462
|
import { defineCommand as defineCommand17 } from "citty";
|
|
12468
12463
|
|
|
12469
12464
|
// src/plan-loop/dispatch.ts
|
|
12470
12465
|
import { mkdir as mkdir7, readFile as readFile28 } from "fs/promises";
|
|
12471
|
-
import
|
|
12466
|
+
import path52 from "path";
|
|
12472
12467
|
var RUN_CATALOG = [
|
|
12473
12468
|
"run-plan",
|
|
12474
12469
|
"run-plan-all",
|
|
@@ -12482,7 +12477,7 @@ var RUN_CATALOG = [
|
|
|
12482
12477
|
"kit-staging"
|
|
12483
12478
|
];
|
|
12484
12479
|
var FIRST_CLASS_SLASH_COMMANDS = ["run-plan", "run-plan-all"];
|
|
12485
|
-
var RUN_PROMOTE_BLOCKED =
|
|
12480
|
+
var RUN_PROMOTE_BLOCKED = RESERVED_GATE_IDS;
|
|
12486
12481
|
function normalizeSlashName(raw) {
|
|
12487
12482
|
return raw.replace(/^\//, "").replace(/\.md$/i, "").trim();
|
|
12488
12483
|
}
|
|
@@ -12512,7 +12507,7 @@ function rewriteRootArgvToRun(argv) {
|
|
|
12512
12507
|
return next;
|
|
12513
12508
|
}
|
|
12514
12509
|
function commandMarkdownPath(root, name) {
|
|
12515
|
-
return
|
|
12510
|
+
return path52.join(root, ".cursor", "commands", `${normalizeSlashName(name)}.md`);
|
|
12516
12511
|
}
|
|
12517
12512
|
function buildDispatchPrompt(commandBody) {
|
|
12518
12513
|
return [
|
|
@@ -12546,19 +12541,7 @@ function stamp2() {
|
|
|
12546
12541
|
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}${p(d.getMilliseconds(), 3)}`;
|
|
12547
12542
|
}
|
|
12548
12543
|
function cursorAgentDispatchArgs(opts) {
|
|
12549
|
-
|
|
12550
|
-
"-p",
|
|
12551
|
-
"--force",
|
|
12552
|
-
"--sandbox",
|
|
12553
|
-
"disabled",
|
|
12554
|
-
"--output-format",
|
|
12555
|
-
"stream-json",
|
|
12556
|
-
"--workspace",
|
|
12557
|
-
opts.workspace
|
|
12558
|
-
];
|
|
12559
|
-
if (opts.model) args.push("--model", opts.model);
|
|
12560
|
-
args.push(opts.prompt);
|
|
12561
|
-
return args;
|
|
12544
|
+
return cursorAgentHeadlessArgs(opts);
|
|
12562
12545
|
}
|
|
12563
12546
|
function claudeDispatchArgs(opts = {}) {
|
|
12564
12547
|
return claudeHeadlessArgs(opts);
|
|
@@ -12612,20 +12595,20 @@ async function runHeadlessDispatch(opts) {
|
|
|
12612
12595
|
});
|
|
12613
12596
|
}
|
|
12614
12597
|
async function ensureDispatchLogPath(root) {
|
|
12615
|
-
const logDir =
|
|
12598
|
+
const logDir = path52.join(root, ".cursor", "loop-logs");
|
|
12616
12599
|
await mkdir7(logDir, { recursive: true });
|
|
12617
|
-
return
|
|
12600
|
+
return path52.join(logDir, `run-${stamp2()}.log`);
|
|
12618
12601
|
}
|
|
12619
12602
|
|
|
12620
12603
|
// src/commands/run.ts
|
|
12621
12604
|
function displayPath(target, home = homedir4()) {
|
|
12622
|
-
const resolvedHome =
|
|
12605
|
+
const resolvedHome = path53.resolve(home);
|
|
12623
12606
|
if (target === resolvedHome) return "~";
|
|
12624
|
-
const prefix = resolvedHome.endsWith(
|
|
12625
|
-
return target.startsWith(prefix) ? `~${
|
|
12607
|
+
const prefix = resolvedHome.endsWith(path53.sep) ? resolvedHome : `${resolvedHome}${path53.sep}`;
|
|
12608
|
+
return target.startsWith(prefix) ? `~${path53.sep}${target.slice(prefix.length)}` : target;
|
|
12626
12609
|
}
|
|
12627
12610
|
async function executeRun(input) {
|
|
12628
|
-
const root =
|
|
12611
|
+
const root = path53.resolve(input.cwd);
|
|
12629
12612
|
const slash = normalizeSlashName(input.slash);
|
|
12630
12613
|
if (!slash) {
|
|
12631
12614
|
return { exitCode: 1, error: "Provide a slash: agent-kit run <slash>" };
|
|
@@ -12676,7 +12659,7 @@ async function executeRun(input) {
|
|
|
12676
12659
|
const stdout = [
|
|
12677
12660
|
"--dry-run: no agent will be started.",
|
|
12678
12661
|
`Slash: ${slash}`,
|
|
12679
|
-
`Command file: ${
|
|
12662
|
+
`Command file: ${path53.relative(root, file.path)}`,
|
|
12680
12663
|
`Backend: ${detected.id} (${displayPath(detected.bin)})`,
|
|
12681
12664
|
"Prompt:",
|
|
12682
12665
|
prompt
|
|
@@ -12685,8 +12668,8 @@ async function executeRun(input) {
|
|
|
12685
12668
|
}
|
|
12686
12669
|
const logPath = await ensureDispatchLogPath(root);
|
|
12687
12670
|
console.log(`Dispatch: ${slash} via ${detected.id}`);
|
|
12688
|
-
console.log(`Command file: ${
|
|
12689
|
-
console.log(`Log: ${
|
|
12671
|
+
console.log(`Command file: ${path53.relative(root, file.path)}`);
|
|
12672
|
+
console.log(`Log: ${path53.relative(root, logPath)}`);
|
|
12690
12673
|
try {
|
|
12691
12674
|
const dispatch = {
|
|
12692
12675
|
backendId: detected.id,
|
|
@@ -12843,7 +12826,7 @@ var scanCommand = defineCommand18({
|
|
|
12843
12826
|
import { spawn as spawn9 } from "child_process";
|
|
12844
12827
|
import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile29, writeFile as writeFile13 } from "fs/promises";
|
|
12845
12828
|
import { homedir as homedir5 } from "os";
|
|
12846
|
-
import
|
|
12829
|
+
import path54 from "path";
|
|
12847
12830
|
import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
12848
12831
|
import { defineCommand as defineCommand19 } from "citty";
|
|
12849
12832
|
import { cyan as cyan5, green as green4, yellow as yellow3 } from "kolorist";
|
|
@@ -12852,9 +12835,9 @@ var SETUP_GLOBAL_MARKER = "# agent-kit setup-global";
|
|
|
12852
12835
|
function planSetupGlobalSteps(env, options = {}) {
|
|
12853
12836
|
const homeDir = options.homeDir ?? homedir5();
|
|
12854
12837
|
const packageSpec = options.packageSpec ?? pinnedCliSpec();
|
|
12855
|
-
const npmGlobalDir =
|
|
12856
|
-
const npmGlobalBin =
|
|
12857
|
-
const npmrcPath =
|
|
12838
|
+
const npmGlobalDir = path54.join(homeDir, NPM_GLOBAL_DIR_NAME);
|
|
12839
|
+
const npmGlobalBin = path54.join(npmGlobalDir, "bin");
|
|
12840
|
+
const npmrcPath = path54.join(homeDir, ".npmrc");
|
|
12858
12841
|
const npmrcPrefixValue = `~/${NPM_GLOBAL_DIR_NAME}`;
|
|
12859
12842
|
const pathExportLine = `export PATH="${npmGlobalBin}:$PATH"`;
|
|
12860
12843
|
const shellProfile = env.shellProfile;
|
|
@@ -13153,7 +13136,7 @@ var setupGlobalCommand = defineCommand19({
|
|
|
13153
13136
|
});
|
|
13154
13137
|
|
|
13155
13138
|
// src/commands/status.ts
|
|
13156
|
-
import
|
|
13139
|
+
import path55 from "path";
|
|
13157
13140
|
import { defineCommand as defineCommand20 } from "citty";
|
|
13158
13141
|
function profileStatus(profile) {
|
|
13159
13142
|
if (!profile) return { origin: "none", evidence: [], profile: null };
|
|
@@ -13184,11 +13167,11 @@ var statusCommand = defineCommand20({
|
|
|
13184
13167
|
}
|
|
13185
13168
|
},
|
|
13186
13169
|
async run({ args }) {
|
|
13187
|
-
const rootDir =
|
|
13170
|
+
const rootDir = path55.resolve(args.cwd);
|
|
13188
13171
|
const [manifest, rawProfile, scan, env] = await Promise.all([
|
|
13189
13172
|
loadAgentKitManifest(rootDir),
|
|
13190
13173
|
readJson(
|
|
13191
|
-
|
|
13174
|
+
path55.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
13192
13175
|
),
|
|
13193
13176
|
runScanner(rootDir),
|
|
13194
13177
|
assessEnvironment()
|
|
@@ -13296,17 +13279,7 @@ var updateCommand = defineCommand21({
|
|
|
13296
13279
|
description: "Apply even when this CLI is older than the registry it is syncing from; the manifest is then stamped with this CLI's version, not the registry's (factory/dev only)",
|
|
13297
13280
|
default: false
|
|
13298
13281
|
},
|
|
13299
|
-
|
|
13300
|
-
type: "boolean",
|
|
13301
|
-
alias: "y",
|
|
13302
|
-
description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
|
|
13303
|
-
default: false
|
|
13304
|
-
},
|
|
13305
|
-
"force-root": {
|
|
13306
|
-
type: "boolean",
|
|
13307
|
-
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
13308
|
-
default: false
|
|
13309
|
-
},
|
|
13282
|
+
...NON_INTERACTIVE_ROOT_ARGS,
|
|
13310
13283
|
...REGISTRY_CLI_ARGS
|
|
13311
13284
|
},
|
|
13312
13285
|
async run({ args }) {
|
|
@@ -13434,7 +13407,7 @@ ${err.recovery}
|
|
|
13434
13407
|
|
|
13435
13408
|
// src/commands/validate.ts
|
|
13436
13409
|
import { readFile as readFile30 } from "fs/promises";
|
|
13437
|
-
import
|
|
13410
|
+
import path56 from "path";
|
|
13438
13411
|
import { defineCommand as defineCommand22 } from "citty";
|
|
13439
13412
|
|
|
13440
13413
|
// src/invariants/plan-schema.ts
|
|
@@ -13481,7 +13454,7 @@ function validatePlanFrontmatterText(text) {
|
|
|
13481
13454
|
// src/commands/validate.ts
|
|
13482
13455
|
async function resolveEditedPath(cwd, explicit) {
|
|
13483
13456
|
if (explicit) {
|
|
13484
|
-
const filePath2 =
|
|
13457
|
+
const filePath2 = path56.resolve(cwd, explicit);
|
|
13485
13458
|
try {
|
|
13486
13459
|
return { filePath: filePath2, content: await readFile30(filePath2, "utf8") };
|
|
13487
13460
|
} catch {
|
|
@@ -13491,7 +13464,7 @@ async function resolveEditedPath(cwd, explicit) {
|
|
|
13491
13464
|
const payload = await readStdinJson();
|
|
13492
13465
|
const rel = typeof payload.file_path === "string" && payload.file_path || typeof payload.path === "string" && payload.path || typeof payload.file === "string" && payload.file || "";
|
|
13493
13466
|
if (!rel) return null;
|
|
13494
|
-
const filePath =
|
|
13467
|
+
const filePath = path56.isAbsolute(rel) ? rel : path56.resolve(cwd, rel);
|
|
13495
13468
|
try {
|
|
13496
13469
|
return { filePath, content: await readFile30(filePath, "utf8") };
|
|
13497
13470
|
} catch {
|
|
@@ -13521,7 +13494,7 @@ var validateCommand = defineCommand22({
|
|
|
13521
13494
|
async run({ args }) {
|
|
13522
13495
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
13523
13496
|
const fileArg = typeof args.file === "string" ? args.file : void 0;
|
|
13524
|
-
const filePath = fileArg ?
|
|
13497
|
+
const filePath = fileArg ? path56.resolve(cwd, fileArg) : path56.join(path56.resolve(cwd), ".cursor", "HANDOFF.md");
|
|
13525
13498
|
let content = "";
|
|
13526
13499
|
try {
|
|
13527
13500
|
content = await readFile30(filePath, "utf8");
|
|
@@ -13554,7 +13527,7 @@ var validateCommand = defineCommand22({
|
|
|
13554
13527
|
process.exitCode = 2;
|
|
13555
13528
|
return;
|
|
13556
13529
|
}
|
|
13557
|
-
const filePath =
|
|
13530
|
+
const filePath = path56.resolve(cwd, fileArg);
|
|
13558
13531
|
let content = "";
|
|
13559
13532
|
try {
|
|
13560
13533
|
content = await readFile30(filePath, "utf8");
|
|
@@ -13576,7 +13549,7 @@ var validateCommand = defineCommand22({
|
|
|
13576
13549
|
},
|
|
13577
13550
|
async run({ args }) {
|
|
13578
13551
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
13579
|
-
const resolved = await resolveEditedPath(
|
|
13552
|
+
const resolved = await resolveEditedPath(path56.resolve(cwd));
|
|
13580
13553
|
if (!resolved) {
|
|
13581
13554
|
console.log(JSON.stringify({}));
|
|
13582
13555
|
return;
|