@dadado/agent-kit-cli 5.10.3 → 5.12.0
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 +544 -566
- 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",
|
|
@@ -342,7 +345,16 @@ var KNOWN_SHIPPED_OVERLAY_HASHES = /* @__PURE__ */ new Set([
|
|
|
342
345
|
"18426bc3bc8fdf0ab7577d92e31310faf3b83001f8e6e6fe5ef1a1043230e178",
|
|
343
346
|
"24cc918e7ffa54df7f6007989fe62b83af1035c9bf6071cac8c4f848e28b04ca",
|
|
344
347
|
"76757eae42daccc0d82c9630eab31041c6d6ea8fe4b4a47843574a6057dd2a4b",
|
|
345
|
-
"88db17beb9ce2b95b2ce2c1df753b96933024a464cc847770d3f05cd7de2872c"
|
|
348
|
+
"88db17beb9ce2b95b2ce2c1df753b96933024a464cc847770d3f05cd7de2872c",
|
|
349
|
+
"71fff34e9392692d58f3dc92ed65d070f0b476446e0a183a8fbad66d46f3a785",
|
|
350
|
+
"73e3ca429cb522b0877f0d6d46bedbaf270f94bc63bb87f4e0fc7c512bf36e9c",
|
|
351
|
+
"9a61f7432cf66be53d42b5d6f3c67ea71dd5efa1cc18d77d996f08ffea873373",
|
|
352
|
+
"fd2a8867de62f83c89ae11295cae5b5699360b5205f9726c2c47c064318bd7a4",
|
|
353
|
+
"364126473e089ed09a71e3f9eed80b1d602b35b4baa669b0a4f4be0519a787f8",
|
|
354
|
+
"cb14c0162e66ac565679089a566a725b26989db3536b7dba89bff77cb6010969",
|
|
355
|
+
"ebf490086ea45a7b342b3ec7408c9a82b22031daaefddf4425b6e6e931f6d6c8",
|
|
356
|
+
"3077e013297c7dd1e32eb2fa86e1ffa1de7d8740058652782bb8d391782eed71",
|
|
357
|
+
"7e0fa1bb73c8b6f1358cae19477304d85edfb7d4d9a7faf37c00e0578ed79edf"
|
|
346
358
|
]);
|
|
347
359
|
|
|
348
360
|
// src/lifecycle/paths.ts
|
|
@@ -713,6 +725,11 @@ import { mkdir as mkdir4, readFile as readFile4, rename, rm, stat, writeFile as
|
|
|
713
725
|
import { homedir } from "os";
|
|
714
726
|
import path5 from "path";
|
|
715
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
|
|
716
733
|
var execFileAsync = promisify(execFile);
|
|
717
734
|
var LOCK_TIMEOUT_MS = 6e4;
|
|
718
735
|
var LOCK_RETRY_MS = 200;
|
|
@@ -871,7 +888,7 @@ function cacheKey(url, ref) {
|
|
|
871
888
|
return createHash2("sha256").update(`${url}@${ref}`).digest("hex").slice(0, 16);
|
|
872
889
|
}
|
|
873
890
|
async function hasRegistryIndex(root) {
|
|
874
|
-
return fileExists(path5.join(root,
|
|
891
|
+
return fileExists(path5.join(root, REGISTRY_INDEX_REL));
|
|
875
892
|
}
|
|
876
893
|
async function cloneRegistry(url, ref, dest) {
|
|
877
894
|
const { mkdir: mkdir9 } = await import("fs/promises");
|
|
@@ -919,7 +936,7 @@ async function resolveRegistryRoot(options) {
|
|
|
919
936
|
if (options.registryPath) {
|
|
920
937
|
const root = path5.resolve(options.registryPath);
|
|
921
938
|
if (!await hasRegistryIndex(root)) {
|
|
922
|
-
throw new Error(`No
|
|
939
|
+
throw new Error(`No ${REGISTRY_INDEX_REL} under --registry ${root}`);
|
|
923
940
|
}
|
|
924
941
|
return { root, source: "flag" };
|
|
925
942
|
}
|
|
@@ -927,7 +944,7 @@ async function resolveRegistryRoot(options) {
|
|
|
927
944
|
if (envPath) {
|
|
928
945
|
const root = path5.resolve(envPath);
|
|
929
946
|
if (!await hasRegistryIndex(root)) {
|
|
930
|
-
throw new Error(`No
|
|
947
|
+
throw new Error(`No ${REGISTRY_INDEX_REL} under AGENT_KIT_REGISTRY=${root}`);
|
|
931
948
|
}
|
|
932
949
|
return { root, source: "env", url: options.registryUrl, ref: options.registryRef };
|
|
933
950
|
}
|
|
@@ -951,7 +968,7 @@ async function resolveRegistryRoot(options) {
|
|
|
951
968
|
} else {
|
|
952
969
|
await cloneRegistry(url, ref, dest);
|
|
953
970
|
if (!await hasRegistryIndex(dest)) {
|
|
954
|
-
throw new Error(`Cloned ${url}@${ref} but
|
|
971
|
+
throw new Error(`Cloned ${url}@${ref} but ${REGISTRY_INDEX_REL} is missing`);
|
|
955
972
|
}
|
|
956
973
|
}
|
|
957
974
|
ownershipTransferred = true;
|
|
@@ -1201,7 +1218,7 @@ async function loadAgentKitManifest(cwd) {
|
|
|
1201
1218
|
// src/registry/client.ts
|
|
1202
1219
|
import path7 from "path";
|
|
1203
1220
|
async function loadRegistry(rootDir) {
|
|
1204
|
-
const indexPath = path7.join(rootDir,
|
|
1221
|
+
const indexPath = path7.join(rootDir, REGISTRY_INDEX_REL);
|
|
1205
1222
|
const index = await readJson(indexPath);
|
|
1206
1223
|
if (!index) throw new Error(`Registry not found at ${indexPath}`);
|
|
1207
1224
|
return index;
|
|
@@ -1318,7 +1335,7 @@ async function installSkill(registryRoot, projectRoot, skill, options = {}) {
|
|
|
1318
1335
|
return stats;
|
|
1319
1336
|
}
|
|
1320
1337
|
async function installSkillsByIds(registryRoot, projectRoot, skillIds, options = {}) {
|
|
1321
|
-
const index = await readJson(path8.join(registryRoot,
|
|
1338
|
+
const index = await readJson(path8.join(registryRoot, REGISTRY_INDEX_REL));
|
|
1322
1339
|
const stats = emptyStats();
|
|
1323
1340
|
if (!index) return stats;
|
|
1324
1341
|
const pool = allSkills(index);
|
|
@@ -2117,22 +2134,40 @@ import { defineCommand as defineCommand3 } from "citty";
|
|
|
2117
2134
|
|
|
2118
2135
|
// src/lifecycle/cursor-update-awareness.ts
|
|
2119
2136
|
import { access as access2, readFile as readFile7 } from "fs/promises";
|
|
2137
|
+
import path11 from "path";
|
|
2138
|
+
|
|
2139
|
+
// src/lifecycle/context-config.ts
|
|
2120
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
|
|
2121
2156
|
var DEFAULT_CURSOR_CHANGELOG_URL = "https://cursor.com/changelog";
|
|
2122
2157
|
var CHANGELOG_FETCH_TIMEOUT_MS = 15e3;
|
|
2123
2158
|
var CURSOR_VERSION_MAJOR_MAX = 20;
|
|
2124
|
-
var INVENTORY_REL =
|
|
2125
|
-
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");
|
|
2126
2161
|
async function resolveInventoryRoot(cwd) {
|
|
2127
|
-
let dir =
|
|
2162
|
+
let dir = path11.resolve(cwd);
|
|
2128
2163
|
for (; ; ) {
|
|
2129
2164
|
try {
|
|
2130
|
-
await access2(
|
|
2165
|
+
await access2(path11.join(dir, INVENTORY_REL));
|
|
2131
2166
|
return dir;
|
|
2132
2167
|
} catch {
|
|
2133
2168
|
}
|
|
2134
|
-
if (await fileExists(
|
|
2135
|
-
const parent =
|
|
2169
|
+
if (await fileExists(path11.join(dir, ".git"))) break;
|
|
2170
|
+
const parent = path11.dirname(dir);
|
|
2136
2171
|
if (parent === dir) break;
|
|
2137
2172
|
dir = parent;
|
|
2138
2173
|
}
|
|
@@ -2168,18 +2203,7 @@ function readCursorUpdateCheckPrefs(config) {
|
|
|
2168
2203
|
changelogUrl
|
|
2169
2204
|
};
|
|
2170
2205
|
}
|
|
2171
|
-
function intervalElapsed(lastCheckedAt, intervalDays) {
|
|
2172
|
-
if (!lastCheckedAt) return true;
|
|
2173
|
-
const last = Date.parse(lastCheckedAt);
|
|
2174
|
-
if (Number.isNaN(last)) return true;
|
|
2175
|
-
return Date.now() - last >= intervalDays * 24 * 60 * 60 * 1e3;
|
|
2176
|
-
}
|
|
2177
|
-
async function loadContextConfig(cwd) {
|
|
2178
|
-
const configPath = path10.join(cwd, ".cursor", "context", "config.json");
|
|
2179
|
-
return readJson(configPath);
|
|
2180
|
-
}
|
|
2181
2206
|
async function stampCursorUpdateCheck(cwd, patch) {
|
|
2182
|
-
const configPath = path10.join(cwd, ".cursor", "context", "config.json");
|
|
2183
2207
|
const existing = await loadContextConfig(cwd) ?? {};
|
|
2184
2208
|
const prev = existing.cursorUpdateCheck && typeof existing.cursorUpdateCheck === "object" ? { ...existing.cursorUpdateCheck } : {};
|
|
2185
2209
|
const nextSeen = patch.lastSeenCursorVersion !== void 0 ? patch.lastSeenCursorVersion : void 0;
|
|
@@ -2189,7 +2213,7 @@ async function stampCursorUpdateCheck(cwd, patch) {
|
|
|
2189
2213
|
...prev,
|
|
2190
2214
|
lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2191
2215
|
};
|
|
2192
|
-
await writeJson(
|
|
2216
|
+
await writeJson(contextConfigPath(cwd), existing);
|
|
2193
2217
|
return;
|
|
2194
2218
|
}
|
|
2195
2219
|
existing.cursorUpdateCheck = {
|
|
@@ -2198,7 +2222,7 @@ async function stampCursorUpdateCheck(cwd, patch) {
|
|
|
2198
2222
|
lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2199
2223
|
...nextSeen !== void 0 ? { lastSeenCursorVersion: nextSeen } : {}
|
|
2200
2224
|
};
|
|
2201
|
-
await writeJson(
|
|
2225
|
+
await writeJson(contextConfigPath(cwd), existing);
|
|
2202
2226
|
}
|
|
2203
2227
|
function isPlausibleCursorVersion(version) {
|
|
2204
2228
|
const parts = String(version).split(".");
|
|
@@ -2448,8 +2472,8 @@ async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
|
2448
2472
|
message: missingInventoryMessage()
|
|
2449
2473
|
});
|
|
2450
2474
|
}
|
|
2451
|
-
const inventoryPath =
|
|
2452
|
-
const featuresPath =
|
|
2475
|
+
const inventoryPath = path11.join(inventoryRoot, INVENTORY_REL);
|
|
2476
|
+
const featuresPath = path11.join(inventoryRoot, FEATURES_REL);
|
|
2453
2477
|
let inventoryMd;
|
|
2454
2478
|
try {
|
|
2455
2479
|
inventoryMd = await readFile7(inventoryPath, "utf8");
|
|
@@ -2662,14 +2686,14 @@ var cursorAwarenessCommand = defineCommand3({
|
|
|
2662
2686
|
// src/commands/dashboard-broadcast.ts
|
|
2663
2687
|
import { spawn as spawn3 } from "child_process";
|
|
2664
2688
|
import { access as access4 } from "fs/promises";
|
|
2665
|
-
import
|
|
2689
|
+
import path13 from "path";
|
|
2666
2690
|
import { defineCommand as defineCommand5 } from "citty";
|
|
2667
2691
|
|
|
2668
2692
|
// src/commands/dashboard.ts
|
|
2669
2693
|
import { execFileSync, spawn as spawn2 } from "child_process";
|
|
2670
2694
|
import { existsSync, readFileSync as readFileSync2, realpathSync } from "fs";
|
|
2671
2695
|
import { access as access3 } from "fs/promises";
|
|
2672
|
-
import
|
|
2696
|
+
import path12 from "path";
|
|
2673
2697
|
import { fileURLToPath } from "url";
|
|
2674
2698
|
import { defineCommand as defineCommand4 } from "citty";
|
|
2675
2699
|
|
|
@@ -2679,7 +2703,7 @@ import { networkInterfaces } from "os";
|
|
|
2679
2703
|
import { resolve } from "path";
|
|
2680
2704
|
var REPO_ROOT_ENV = "MISSION_CONTROL_REPO_ROOT";
|
|
2681
2705
|
var KIT_ROOT_ENV_KEYS = Object.freeze(["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]);
|
|
2682
|
-
var
|
|
2706
|
+
var CONTEXT_CONFIG_REL2 = ".cursor/context/config.json";
|
|
2683
2707
|
var CONFIG_PERSONA_IDS = Object.freeze(["autopilot", "night-shift", "ghost-runner"]);
|
|
2684
2708
|
var CONFIG_PERSONA_MODES = Object.freeze(["continue-plan", "run-plan", "cli-run-plan"]);
|
|
2685
2709
|
var CONFIG_REVIEW_BACKENDS = Object.freeze(["auto", "claude", "cursor"]);
|
|
@@ -2692,7 +2716,7 @@ function resolveContextConfigPath(repoRoot, fsHooks = {}) {
|
|
|
2692
2716
|
if (typeof repoRoot !== "string" || !repoRoot) {
|
|
2693
2717
|
return { ok: false, error: "invalid repo root" };
|
|
2694
2718
|
}
|
|
2695
|
-
const abs = resolve(repoRoot,
|
|
2719
|
+
const abs = resolve(repoRoot, CONTEXT_CONFIG_REL2);
|
|
2696
2720
|
const contextDir = resolve(repoRoot, ".cursor", "context");
|
|
2697
2721
|
const absNorm = abs.replace(/\\/g, "/");
|
|
2698
2722
|
if (!absNorm.endsWith("/.cursor/context/config.json")) {
|
|
@@ -2758,7 +2782,7 @@ function readPreferredBrowserFromConfig(configPath, fsHooks = {}) {
|
|
|
2758
2782
|
|
|
2759
2783
|
// src/commands/dashboard.ts
|
|
2760
2784
|
function readPreferredBrowserFromWorkspace(cwd, readFile31 = readFileSync2) {
|
|
2761
|
-
const resolved = resolveContextConfigPath(
|
|
2785
|
+
const resolved = resolveContextConfigPath(path12.resolve(cwd), {
|
|
2762
2786
|
existsSync,
|
|
2763
2787
|
realpathSync
|
|
2764
2788
|
});
|
|
@@ -2780,61 +2804,61 @@ function applyDashboardOpenEnv(env, opts) {
|
|
|
2780
2804
|
return next;
|
|
2781
2805
|
}
|
|
2782
2806
|
function dashboardSpawnEnv(env, snapshotRoot, opts) {
|
|
2783
|
-
const root =
|
|
2807
|
+
const root = path12.resolve(snapshotRoot);
|
|
2784
2808
|
return applyDashboardOpenEnv({ ...env, MISSION_CONTROL_REPO_ROOT: root }, { ...opts, cwd: root });
|
|
2785
2809
|
}
|
|
2786
2810
|
function dashboardProcessTitle(snapshotRoot) {
|
|
2787
|
-
const base =
|
|
2811
|
+
const base = path12.basename(path12.resolve(snapshotRoot)) || "workspace";
|
|
2788
2812
|
return `Mission Control \xB7 ${base}`;
|
|
2789
2813
|
}
|
|
2790
2814
|
function bundledDashboardCandidates(filename, moduleUrl = import.meta.url) {
|
|
2791
|
-
const here =
|
|
2815
|
+
const here = path12.dirname(fileURLToPath(moduleUrl));
|
|
2792
2816
|
return [
|
|
2793
2817
|
// Published / built: <pkg>/dist/index.js -> <pkg>/dashboard/
|
|
2794
|
-
|
|
2818
|
+
path12.join(here, "..", "dashboard", filename),
|
|
2795
2819
|
// Dev source: packages/cli/src/commands/*.ts -> packages/cli/dashboard/ (sync copy)
|
|
2796
|
-
|
|
2820
|
+
path12.join(here, "..", "..", "dashboard", filename),
|
|
2797
2821
|
// From src/commands: packages/dashboard/ (unused layout); from dist: two levels above package
|
|
2798
|
-
|
|
2822
|
+
path12.join(here, "..", "..", "..", "dashboard", filename)
|
|
2799
2823
|
];
|
|
2800
2824
|
}
|
|
2801
2825
|
async function firstExisting(candidates) {
|
|
2802
2826
|
for (const candidate2 of candidates) {
|
|
2803
2827
|
try {
|
|
2804
2828
|
await access3(candidate2);
|
|
2805
|
-
return
|
|
2829
|
+
return path12.resolve(candidate2);
|
|
2806
2830
|
} catch {
|
|
2807
2831
|
}
|
|
2808
2832
|
}
|
|
2809
2833
|
return null;
|
|
2810
2834
|
}
|
|
2811
2835
|
async function findDashboardStart(cwd, env = process.env, options = {}) {
|
|
2812
|
-
let dir =
|
|
2836
|
+
let dir = path12.resolve(cwd);
|
|
2813
2837
|
for (; ; ) {
|
|
2814
|
-
const candidate2 =
|
|
2838
|
+
const candidate2 = path12.join(dir, "dashboard", "start.mjs");
|
|
2815
2839
|
try {
|
|
2816
2840
|
await access3(candidate2);
|
|
2817
2841
|
return candidate2;
|
|
2818
2842
|
} catch {
|
|
2819
2843
|
}
|
|
2820
|
-
const parent =
|
|
2844
|
+
const parent = path12.dirname(dir);
|
|
2821
2845
|
if (parent === dir) break;
|
|
2822
2846
|
dir = parent;
|
|
2823
2847
|
}
|
|
2824
2848
|
for (const key of ["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]) {
|
|
2825
2849
|
const base = env[key];
|
|
2826
2850
|
if (!base || typeof base !== "string" || !base.trim()) continue;
|
|
2827
|
-
const candidate2 =
|
|
2851
|
+
const candidate2 = path12.join(path12.resolve(base.trim()), "dashboard", "start.mjs");
|
|
2828
2852
|
try {
|
|
2829
2853
|
await access3(candidate2);
|
|
2830
2854
|
return candidate2;
|
|
2831
2855
|
} catch {
|
|
2832
2856
|
}
|
|
2833
2857
|
}
|
|
2834
|
-
const sibling =
|
|
2858
|
+
const sibling = path12.join(path12.resolve(cwd), "..", "agent-kit", "dashboard", "start.mjs");
|
|
2835
2859
|
try {
|
|
2836
2860
|
await access3(sibling);
|
|
2837
|
-
return
|
|
2861
|
+
return path12.resolve(sibling);
|
|
2838
2862
|
} catch {
|
|
2839
2863
|
}
|
|
2840
2864
|
return firstExisting(
|
|
@@ -2842,13 +2866,13 @@ async function findDashboardStart(cwd, env = process.env, options = {}) {
|
|
|
2842
2866
|
);
|
|
2843
2867
|
}
|
|
2844
2868
|
function resolveDashboardSnapshotRoot(cwd) {
|
|
2845
|
-
const abs =
|
|
2869
|
+
const abs = path12.resolve(cwd);
|
|
2846
2870
|
let dir = abs;
|
|
2847
2871
|
for (; ; ) {
|
|
2848
|
-
if (existsSync(
|
|
2872
|
+
if (existsSync(path12.join(dir, ".cursor", "agent-kit.json"))) {
|
|
2849
2873
|
return dir;
|
|
2850
2874
|
}
|
|
2851
|
-
const parent =
|
|
2875
|
+
const parent = path12.dirname(dir);
|
|
2852
2876
|
if (parent === dir) break;
|
|
2853
2877
|
dir = parent;
|
|
2854
2878
|
}
|
|
@@ -2858,7 +2882,7 @@ function resolveDashboardSnapshotRoot(cwd) {
|
|
|
2858
2882
|
timeout: 5e3,
|
|
2859
2883
|
stdio: ["ignore", "pipe", "ignore"]
|
|
2860
2884
|
}).trim();
|
|
2861
|
-
if (top) return
|
|
2885
|
+
if (top) return path12.resolve(top);
|
|
2862
2886
|
} catch {
|
|
2863
2887
|
}
|
|
2864
2888
|
return abs;
|
|
@@ -2866,7 +2890,7 @@ function resolveDashboardSnapshotRoot(cwd) {
|
|
|
2866
2890
|
function runStartScript(startPath, env) {
|
|
2867
2891
|
return new Promise((resolve3, reject) => {
|
|
2868
2892
|
const child = spawn2(process.execPath, [startPath], {
|
|
2869
|
-
cwd:
|
|
2893
|
+
cwd: path12.dirname(path12.dirname(startPath)),
|
|
2870
2894
|
env,
|
|
2871
2895
|
stdio: "inherit"
|
|
2872
2896
|
});
|
|
@@ -2934,30 +2958,30 @@ var dashboardCommand = defineCommand4({
|
|
|
2934
2958
|
|
|
2935
2959
|
// src/commands/dashboard-broadcast.ts
|
|
2936
2960
|
async function findDashboardBroadcastStart(cwd, env = process.env, options = {}) {
|
|
2937
|
-
let dir =
|
|
2961
|
+
let dir = path13.resolve(cwd);
|
|
2938
2962
|
for (; ; ) {
|
|
2939
|
-
const candidate2 =
|
|
2963
|
+
const candidate2 = path13.join(dir, "dashboard", "start-broadcast.mjs");
|
|
2940
2964
|
try {
|
|
2941
2965
|
await access4(candidate2);
|
|
2942
2966
|
return candidate2;
|
|
2943
2967
|
} catch {
|
|
2944
2968
|
}
|
|
2945
|
-
const parent =
|
|
2969
|
+
const parent = path13.dirname(dir);
|
|
2946
2970
|
if (parent === dir) break;
|
|
2947
2971
|
dir = parent;
|
|
2948
2972
|
}
|
|
2949
2973
|
for (const key of ["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]) {
|
|
2950
2974
|
const base = env[key];
|
|
2951
2975
|
if (!base || typeof base !== "string" || !base.trim()) continue;
|
|
2952
|
-
const candidate2 =
|
|
2976
|
+
const candidate2 = path13.join(path13.resolve(base.trim()), "dashboard", "start-broadcast.mjs");
|
|
2953
2977
|
try {
|
|
2954
2978
|
await access4(candidate2);
|
|
2955
2979
|
return candidate2;
|
|
2956
2980
|
} catch {
|
|
2957
2981
|
}
|
|
2958
2982
|
}
|
|
2959
|
-
const sibling =
|
|
2960
|
-
|
|
2983
|
+
const sibling = path13.join(
|
|
2984
|
+
path13.resolve(cwd),
|
|
2961
2985
|
"..",
|
|
2962
2986
|
"agent-kit",
|
|
2963
2987
|
"dashboard",
|
|
@@ -2965,7 +2989,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
2965
2989
|
);
|
|
2966
2990
|
try {
|
|
2967
2991
|
await access4(sibling);
|
|
2968
|
-
return
|
|
2992
|
+
return path13.resolve(sibling);
|
|
2969
2993
|
} catch {
|
|
2970
2994
|
}
|
|
2971
2995
|
for (const candidate2 of bundledDashboardCandidates(
|
|
@@ -2974,7 +2998,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
2974
2998
|
)) {
|
|
2975
2999
|
try {
|
|
2976
3000
|
await access4(candidate2);
|
|
2977
|
-
return
|
|
3001
|
+
return path13.resolve(candidate2);
|
|
2978
3002
|
} catch {
|
|
2979
3003
|
}
|
|
2980
3004
|
}
|
|
@@ -2983,7 +3007,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
2983
3007
|
function runStartScript2(startPath, env) {
|
|
2984
3008
|
return new Promise((resolve3, reject) => {
|
|
2985
3009
|
const child = spawn3(process.execPath, [startPath], {
|
|
2986
|
-
cwd:
|
|
3010
|
+
cwd: path13.dirname(path13.dirname(startPath)),
|
|
2987
3011
|
env,
|
|
2988
3012
|
stdio: "inherit"
|
|
2989
3013
|
});
|
|
@@ -3038,7 +3062,7 @@ import { defineCommand as defineCommand6 } from "citty";
|
|
|
3038
3062
|
|
|
3039
3063
|
// src/lifecycle/diff.ts
|
|
3040
3064
|
import { readFile as readFile8 } from "fs/promises";
|
|
3041
|
-
import
|
|
3065
|
+
import path14 from "path";
|
|
3042
3066
|
async function readIfExists(abs) {
|
|
3043
3067
|
try {
|
|
3044
3068
|
return await readFile8(abs, "utf8");
|
|
@@ -3047,7 +3071,7 @@ async function readIfExists(abs) {
|
|
|
3047
3071
|
}
|
|
3048
3072
|
}
|
|
3049
3073
|
async function comparePair(registryRoot, projectRoot, sourceRel, targetRel, protectedGlobs) {
|
|
3050
|
-
const posixTarget = targetRel.split(
|
|
3074
|
+
const posixTarget = targetRel.split(path14.sep).join("/");
|
|
3051
3075
|
if (isProtectedPath(posixTarget, protectedGlobs)) {
|
|
3052
3076
|
return { path: posixTarget, status: "protected" };
|
|
3053
3077
|
}
|
|
@@ -3077,7 +3101,7 @@ async function diffAgainstRegistry(registryRoot, projectRoot, manifest) {
|
|
|
3077
3101
|
const entries = [];
|
|
3078
3102
|
const seen = /* @__PURE__ */ new Set();
|
|
3079
3103
|
const pushUnique = async (sourceRel, targetRel) => {
|
|
3080
|
-
const key = targetRel.split(
|
|
3104
|
+
const key = targetRel.split(path14.sep).join("/");
|
|
3081
3105
|
if (seen.has(key)) return;
|
|
3082
3106
|
seen.add(key);
|
|
3083
3107
|
entries.push(
|
|
@@ -3182,14 +3206,116 @@ var diffCommand = defineCommand6({
|
|
|
3182
3206
|
});
|
|
3183
3207
|
|
|
3184
3208
|
// src/commands/doctor.ts
|
|
3185
|
-
import
|
|
3209
|
+
import path28 from "path";
|
|
3186
3210
|
import { defineCommand as defineCommand7 } from "citty";
|
|
3187
3211
|
|
|
3188
3212
|
// src/invariants/hooks-health.ts
|
|
3189
3213
|
import { execFile as execFile2 } from "child_process";
|
|
3190
|
-
import { constants as constants2, access as access5, readFile as
|
|
3191
|
-
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";
|
|
3192
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
|
|
3193
3319
|
var execFileAsync2 = promisify2(execFile2);
|
|
3194
3320
|
var GIT_HOOK_CANONICAL_NAMES = ["pre-commit", "pre-push", "prepare-commit-msg"];
|
|
3195
3321
|
var EXPECTED_EVENTS = [
|
|
@@ -3216,10 +3342,10 @@ async function isExecutable(p) {
|
|
|
3216
3342
|
}
|
|
3217
3343
|
}
|
|
3218
3344
|
async function resolveAgentKitCli(rootDir) {
|
|
3219
|
-
const root =
|
|
3345
|
+
const root = path16.resolve(rootDir);
|
|
3220
3346
|
const candidates = [
|
|
3221
|
-
|
|
3222
|
-
|
|
3347
|
+
path16.join(root, "node_modules", ".bin", "agent-kit"),
|
|
3348
|
+
path16.join(root, "packages", "cli", "dist", "index.js")
|
|
3223
3349
|
];
|
|
3224
3350
|
for (const c of candidates) {
|
|
3225
3351
|
if (await exists(c)) return c;
|
|
@@ -3239,8 +3365,8 @@ function commandLooksLikeAdapter(command) {
|
|
|
3239
3365
|
return m?.[1] ?? null;
|
|
3240
3366
|
}
|
|
3241
3367
|
async function assessGitHooksInstallDrift(rootDir) {
|
|
3242
|
-
const root =
|
|
3243
|
-
const canonicalDir =
|
|
3368
|
+
const root = path16.resolve(rootDir);
|
|
3369
|
+
const canonicalDir = path16.join(root, "git-hooks");
|
|
3244
3370
|
if (!await exists(canonicalDir)) return [];
|
|
3245
3371
|
let hooksPathCfg = "";
|
|
3246
3372
|
try {
|
|
@@ -3253,12 +3379,12 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
3253
3379
|
} catch {
|
|
3254
3380
|
}
|
|
3255
3381
|
if (hooksPathCfg) {
|
|
3256
|
-
const resolved =
|
|
3257
|
-
if (resolved ===
|
|
3382
|
+
const resolved = path16.isAbsolute(hooksPathCfg) ? path16.normalize(hooksPathCfg) : path16.normalize(path16.join(root, hooksPathCfg));
|
|
3383
|
+
if (resolved === path16.normalize(canonicalDir)) {
|
|
3258
3384
|
return [];
|
|
3259
3385
|
}
|
|
3260
3386
|
}
|
|
3261
|
-
const gitDir =
|
|
3387
|
+
const gitDir = path16.join(root, ".git");
|
|
3262
3388
|
if (!await exists(gitDir)) return [];
|
|
3263
3389
|
try {
|
|
3264
3390
|
const st = await stat2(gitDir);
|
|
@@ -3266,7 +3392,7 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
3266
3392
|
} catch {
|
|
3267
3393
|
return [];
|
|
3268
3394
|
}
|
|
3269
|
-
const installedDir =
|
|
3395
|
+
const installedDir = path16.join(gitDir, "hooks");
|
|
3270
3396
|
const advisories = [];
|
|
3271
3397
|
const installHint = "Install or refresh with: `cp git-hooks/<name> .git/hooks/<name> && chmod +x .git/hooks/<name>` (see git-hooks/README.md)";
|
|
3272
3398
|
let names = [...GIT_HOOK_CANONICAL_NAMES];
|
|
@@ -3279,8 +3405,8 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
3279
3405
|
} catch {
|
|
3280
3406
|
}
|
|
3281
3407
|
for (const name of names) {
|
|
3282
|
-
const canonical =
|
|
3283
|
-
const installed =
|
|
3408
|
+
const canonical = path16.join(canonicalDir, name);
|
|
3409
|
+
const installed = path16.join(installedDir, name);
|
|
3284
3410
|
if (!await exists(canonical)) continue;
|
|
3285
3411
|
if (!await exists(installed)) {
|
|
3286
3412
|
advisories.push(
|
|
@@ -3289,7 +3415,7 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
3289
3415
|
continue;
|
|
3290
3416
|
}
|
|
3291
3417
|
try {
|
|
3292
|
-
const [a, b] = await Promise.all([
|
|
3418
|
+
const [a, b] = await Promise.all([readFile10(canonical, "utf8"), readFile10(installed, "utf8")]);
|
|
3293
3419
|
if (a !== b) {
|
|
3294
3420
|
advisories.push(
|
|
3295
3421
|
`git-hooks drift: \`.git/hooks/${name}\` differs from \`git-hooks/${name}\`. ${installHint}`
|
|
@@ -3304,9 +3430,9 @@ async function assessGitHooksInstallDrift(rootDir) {
|
|
|
3304
3430
|
return advisories;
|
|
3305
3431
|
}
|
|
3306
3432
|
async function assessHooksHealth(rootDir) {
|
|
3307
|
-
const root =
|
|
3433
|
+
const root = path16.resolve(rootDir);
|
|
3308
3434
|
const hooksJsonPath = ".cursor/hooks.json";
|
|
3309
|
-
const hooksJsonAbs =
|
|
3435
|
+
const hooksJsonAbs = path16.join(root, hooksJsonPath);
|
|
3310
3436
|
const reasons = [];
|
|
3311
3437
|
const wiredEvents = [];
|
|
3312
3438
|
const advisories = await assessGitHooksInstallDrift(root);
|
|
@@ -3322,7 +3448,7 @@ async function assessHooksHealth(rootDir) {
|
|
|
3322
3448
|
}
|
|
3323
3449
|
let parsed;
|
|
3324
3450
|
try {
|
|
3325
|
-
parsed = JSON.parse(await
|
|
3451
|
+
parsed = JSON.parse(await readFile10(hooksJsonAbs, "utf8"));
|
|
3326
3452
|
} catch {
|
|
3327
3453
|
return {
|
|
3328
3454
|
status: "degraded",
|
|
@@ -3352,14 +3478,14 @@ async function assessHooksHealth(rootDir) {
|
|
|
3352
3478
|
reasons.push(`missing hook event: ${event}`);
|
|
3353
3479
|
}
|
|
3354
3480
|
}
|
|
3355
|
-
const resolveLib =
|
|
3481
|
+
const resolveLib = path16.join(root, RESOLVE_AGENT_KIT_REL);
|
|
3356
3482
|
if (!await exists(resolveLib)) {
|
|
3357
|
-
reasons.push(
|
|
3483
|
+
reasons.push(`missing \`${RESOLVE_AGENT_KIT_REL}\` (thin adapter resolver)`);
|
|
3358
3484
|
} else if (!await isExecutable(resolveLib)) {
|
|
3359
|
-
reasons.push(
|
|
3485
|
+
reasons.push(`\`${RESOLVE_AGENT_KIT_REL}\` is not executable (chmod +x)`);
|
|
3360
3486
|
}
|
|
3361
3487
|
for (const rel of adapterRels) {
|
|
3362
|
-
const abs =
|
|
3488
|
+
const abs = path16.join(root, rel);
|
|
3363
3489
|
if (!await exists(abs)) {
|
|
3364
3490
|
reasons.push(`missing adapter script: \`${rel}\``);
|
|
3365
3491
|
continue;
|
|
@@ -3400,7 +3526,7 @@ async function assessHooksHealth(rootDir) {
|
|
|
3400
3526
|
|
|
3401
3527
|
// src/lifecycle/check-updates.ts
|
|
3402
3528
|
import { execFile as execFile3 } from "child_process";
|
|
3403
|
-
import
|
|
3529
|
+
import path17 from "path";
|
|
3404
3530
|
import { promisify as promisify3 } from "util";
|
|
3405
3531
|
var execFileAsync3 = promisify3(execFile3);
|
|
3406
3532
|
var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/i;
|
|
@@ -3500,31 +3626,19 @@ function readUpdateCheckPrefs(config) {
|
|
|
3500
3626
|
lastCheckedAt: typeof uc.lastCheckedAt === "string" ? uc.lastCheckedAt : null
|
|
3501
3627
|
};
|
|
3502
3628
|
}
|
|
3503
|
-
function intervalElapsed2(lastCheckedAt, intervalDays) {
|
|
3504
|
-
if (!lastCheckedAt) return true;
|
|
3505
|
-
const last = Date.parse(lastCheckedAt);
|
|
3506
|
-
if (Number.isNaN(last)) return true;
|
|
3507
|
-
const ms = intervalDays * 24 * 60 * 60 * 1e3;
|
|
3508
|
-
return Date.now() - last >= ms;
|
|
3509
|
-
}
|
|
3510
|
-
async function loadContextConfig2(cwd) {
|
|
3511
|
-
const configPath = path15.join(cwd, ".cursor", "context", "config.json");
|
|
3512
|
-
return readJson(configPath);
|
|
3513
|
-
}
|
|
3514
3629
|
async function stampLastCheckedAt(cwd) {
|
|
3515
|
-
const
|
|
3516
|
-
const existing = await loadContextConfig2(cwd) ?? {};
|
|
3630
|
+
const existing = await loadContextConfig(cwd) ?? {};
|
|
3517
3631
|
const prev = existing.updateCheck && typeof existing.updateCheck === "object" ? { ...existing.updateCheck } : {};
|
|
3518
3632
|
existing.updateCheck = {
|
|
3519
3633
|
...DEFAULT_UPDATE_CHECK,
|
|
3520
3634
|
...prev,
|
|
3521
3635
|
lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3522
3636
|
};
|
|
3523
|
-
await writeJson(
|
|
3637
|
+
await writeJson(contextConfigPath(cwd), existing);
|
|
3524
3638
|
}
|
|
3525
3639
|
async function readLocalKitVersion(registryRoot) {
|
|
3526
3640
|
for (const rel of ["packages/cli/package.json", "package.json"]) {
|
|
3527
|
-
const data = await readJson(
|
|
3641
|
+
const data = await readJson(path17.join(registryRoot, rel));
|
|
3528
3642
|
if (data && typeof data.version === "string" && data.version.length > 0) {
|
|
3529
3643
|
return data.version;
|
|
3530
3644
|
}
|
|
@@ -3545,7 +3659,7 @@ async function checkAgainstLocalRegistry(cwd, manifest, options) {
|
|
|
3545
3659
|
status: "error",
|
|
3546
3660
|
installedVersion: manifest.version,
|
|
3547
3661
|
latestVersion: null,
|
|
3548
|
-
registryUrl:
|
|
3662
|
+
registryUrl: path17.resolve(registryPath),
|
|
3549
3663
|
registryRef: "local",
|
|
3550
3664
|
applyRecommended: false,
|
|
3551
3665
|
message: `Failed to resolve --registry: ${msg}`
|
|
@@ -3663,7 +3777,7 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
3663
3777
|
};
|
|
3664
3778
|
}
|
|
3665
3779
|
if (options.respectPrefs) {
|
|
3666
|
-
const config = await
|
|
3780
|
+
const config = await loadContextConfig(cwd);
|
|
3667
3781
|
const prefs = readUpdateCheckPrefs(config);
|
|
3668
3782
|
if (!prefs.enabled) {
|
|
3669
3783
|
return {
|
|
@@ -3676,7 +3790,7 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
3676
3790
|
message: "updateCheck.enabled is false (opt-in). Set true in .cursor/context/config.json to nudge."
|
|
3677
3791
|
};
|
|
3678
3792
|
}
|
|
3679
|
-
if (!
|
|
3793
|
+
if (!intervalElapsed(prefs.lastCheckedAt, prefs.intervalDays)) {
|
|
3680
3794
|
return {
|
|
3681
3795
|
status: "skipped-interval",
|
|
3682
3796
|
installedVersion: manifest.version,
|
|
@@ -3773,7 +3887,7 @@ function runningCliBehindMessage(running, latest) {
|
|
|
3773
3887
|
async function checkRunningCliVsNpmLatest(cwd, options = {}) {
|
|
3774
3888
|
const running = normalizeSemver(KIT_VERSION) ?? KIT_VERSION;
|
|
3775
3889
|
if (options.respectPrefs) {
|
|
3776
|
-
const config = await
|
|
3890
|
+
const config = await loadContextConfig(cwd);
|
|
3777
3891
|
const prefs = readUpdateCheckPrefs(config);
|
|
3778
3892
|
if (!prefs.enabled) {
|
|
3779
3893
|
return {
|
|
@@ -3783,7 +3897,7 @@ async function checkRunningCliVsNpmLatest(cwd, options = {}) {
|
|
|
3783
3897
|
message: "updateCheck.enabled is false (opt-in). Set true in .cursor/context/config.json to nudge."
|
|
3784
3898
|
};
|
|
3785
3899
|
}
|
|
3786
|
-
if (!
|
|
3900
|
+
if (!intervalElapsed(prefs.lastCheckedAt, prefs.intervalDays)) {
|
|
3787
3901
|
return {
|
|
3788
3902
|
status: "skipped-interval",
|
|
3789
3903
|
runningVersion: running,
|
|
@@ -3882,11 +3996,11 @@ async function warnIfRunningCliBehindNpm(cwd, options = {}) {
|
|
|
3882
3996
|
}
|
|
3883
3997
|
|
|
3884
3998
|
// src/readiness/env-checks.ts
|
|
3885
|
-
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";
|
|
3886
4000
|
import { homedir as homedir2 } from "os";
|
|
3887
|
-
import
|
|
4001
|
+
import path18 from "path";
|
|
3888
4002
|
var MIN_NODE_MAJOR = 20;
|
|
3889
|
-
var CLI_PACKAGE_JSON_REL =
|
|
4003
|
+
var CLI_PACKAGE_JSON_REL = path18.join(
|
|
3890
4004
|
"lib",
|
|
3891
4005
|
"node_modules",
|
|
3892
4006
|
"@dadado",
|
|
@@ -3907,7 +4021,7 @@ function expandBinDirVars(raw, binDir) {
|
|
|
3907
4021
|
return raw.replace(/\$\{basedir\}/g, binDir).replace(/\$basedir/g, binDir);
|
|
3908
4022
|
}
|
|
3909
4023
|
function packageJsonFromDistIndex(distIndex) {
|
|
3910
|
-
return
|
|
4024
|
+
return path18.join(path18.dirname(distIndex), "..", "package.json");
|
|
3911
4025
|
}
|
|
3912
4026
|
function parsePackageVersion(contents) {
|
|
3913
4027
|
try {
|
|
@@ -3918,23 +4032,23 @@ function parsePackageVersion(contents) {
|
|
|
3918
4032
|
}
|
|
3919
4033
|
}
|
|
3920
4034
|
async function readBinVersion(binPath, options = {}) {
|
|
3921
|
-
const readFileImpl = options.readFileImpl ?? ((filePath) =>
|
|
3922
|
-
const binDir =
|
|
4035
|
+
const readFileImpl = options.readFileImpl ?? ((filePath) => readFile11(filePath, "utf8"));
|
|
4036
|
+
const binDir = path18.dirname(binPath);
|
|
3923
4037
|
const candidates = [
|
|
3924
|
-
|
|
3925
|
-
|
|
4038
|
+
path18.join(binDir, "..", CLI_PACKAGE_JSON_REL),
|
|
4039
|
+
path18.join(binDir, "..", "node_modules", "@dadado", "agent-kit-cli", "package.json")
|
|
3926
4040
|
];
|
|
3927
4041
|
try {
|
|
3928
4042
|
const contents = await readFileImpl(binPath);
|
|
3929
4043
|
for (const raw of extractDistIndexFromBinScript(contents)) {
|
|
3930
|
-
const distIndex =
|
|
4044
|
+
const distIndex = path18.resolve(binDir, expandBinDirVars(raw, binDir));
|
|
3931
4045
|
candidates.push(packageJsonFromDistIndex(distIndex));
|
|
3932
4046
|
}
|
|
3933
4047
|
} catch {
|
|
3934
4048
|
}
|
|
3935
4049
|
const seen = /* @__PURE__ */ new Set();
|
|
3936
4050
|
for (const candidate2 of candidates) {
|
|
3937
|
-
const resolved =
|
|
4051
|
+
const resolved = path18.resolve(candidate2);
|
|
3938
4052
|
if (seen.has(resolved)) continue;
|
|
3939
4053
|
seen.add(resolved);
|
|
3940
4054
|
try {
|
|
@@ -3948,11 +4062,11 @@ async function readBinVersion(binPath, options = {}) {
|
|
|
3948
4062
|
async function resolveBinOnPath(binName, env, platform) {
|
|
3949
4063
|
const pathVar = env.PATH ?? env.Path ?? "";
|
|
3950
4064
|
if (!pathVar) return null;
|
|
3951
|
-
const dirs = pathVar.split(
|
|
4065
|
+
const dirs = pathVar.split(path18.delimiter).filter(Boolean);
|
|
3952
4066
|
const candidates = platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.exe`, `${binName}.bat`] : [binName];
|
|
3953
4067
|
for (const dir of dirs) {
|
|
3954
4068
|
for (const candidate2 of candidates) {
|
|
3955
|
-
const abs =
|
|
4069
|
+
const abs = path18.join(dir, candidate2);
|
|
3956
4070
|
try {
|
|
3957
4071
|
await access6(abs, platform === "win32" ? void 0 : constants3.X_OK);
|
|
3958
4072
|
return abs;
|
|
@@ -3976,13 +4090,13 @@ function detectShellName(env, platform) {
|
|
|
3976
4090
|
}
|
|
3977
4091
|
const shellPath = env.SHELL;
|
|
3978
4092
|
if (!shellPath) return null;
|
|
3979
|
-
const base =
|
|
4093
|
+
const base = path18.basename(shellPath).trim();
|
|
3980
4094
|
return base || null;
|
|
3981
4095
|
}
|
|
3982
4096
|
function detectShellProfile(env, platform, homeDir) {
|
|
3983
4097
|
const shellName = detectShellName(env, platform);
|
|
3984
|
-
if (shellName === "zsh") return
|
|
3985
|
-
if (shellName === "bash") return
|
|
4098
|
+
if (shellName === "zsh") return path18.join(homeDir, ".zshrc");
|
|
4099
|
+
if (shellName === "bash") return path18.join(homeDir, ".bashrc");
|
|
3986
4100
|
return null;
|
|
3987
4101
|
}
|
|
3988
4102
|
function parseNpmrcPrefix(content, homeDir) {
|
|
@@ -3991,12 +4105,12 @@ function parseNpmrcPrefix(content, homeDir) {
|
|
|
3991
4105
|
if (!captured) return null;
|
|
3992
4106
|
let value = captured.trim().replace(/^["']|["']$/g, "");
|
|
3993
4107
|
if (value.startsWith("~")) {
|
|
3994
|
-
value =
|
|
4108
|
+
value = path18.join(homeDir, value.slice(1));
|
|
3995
4109
|
}
|
|
3996
4110
|
return value || null;
|
|
3997
4111
|
}
|
|
3998
4112
|
function heuristicPrefixFromExecPath(execPath, platform) {
|
|
3999
|
-
const p = platform === "win32" ?
|
|
4113
|
+
const p = platform === "win32" ? path18.win32 : path18.posix;
|
|
4000
4114
|
return platform === "win32" ? p.dirname(execPath) : p.dirname(p.dirname(execPath));
|
|
4001
4115
|
}
|
|
4002
4116
|
async function detectNpmPrefix(options = {}) {
|
|
@@ -4004,12 +4118,12 @@ async function detectNpmPrefix(options = {}) {
|
|
|
4004
4118
|
const platform = options.platform ?? process.platform;
|
|
4005
4119
|
const homeDir = options.homeDir ?? homedir2();
|
|
4006
4120
|
const execPath = options.execPath ?? process.execPath;
|
|
4007
|
-
const readFileImpl = options.readFileImpl ?? ((filePath) =>
|
|
4121
|
+
const readFileImpl = options.readFileImpl ?? ((filePath) => readFile11(filePath, "utf8"));
|
|
4008
4122
|
const envPrefix = env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX;
|
|
4009
4123
|
if (envPrefix?.trim()) {
|
|
4010
4124
|
return { prefix: envPrefix.trim(), source: "env" };
|
|
4011
4125
|
}
|
|
4012
|
-
const userconfigPath = env.NPM_CONFIG_USERCONFIG ??
|
|
4126
|
+
const userconfigPath = env.NPM_CONFIG_USERCONFIG ?? path18.join(homeDir, ".npmrc");
|
|
4013
4127
|
try {
|
|
4014
4128
|
const content = await readFileImpl(userconfigPath);
|
|
4015
4129
|
const npmrcPrefix = parseNpmrcPrefix(content, homeDir);
|
|
@@ -4336,12 +4450,17 @@ function createReadinessReport(scan, options) {
|
|
|
4336
4450
|
}
|
|
4337
4451
|
|
|
4338
4452
|
// src/scanner/safe-fixes.ts
|
|
4339
|
-
import { readFile as
|
|
4340
|
-
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";
|
|
4341
4462
|
|
|
4342
4463
|
// src/scanner/detect-repository.ts
|
|
4343
|
-
import { readFile as readFile11 } from "fs/promises";
|
|
4344
|
-
import path17 from "path";
|
|
4345
4464
|
var CONTEXT_PATHS = [
|
|
4346
4465
|
["README.md", "README"],
|
|
4347
4466
|
["README", "README"],
|
|
@@ -4360,7 +4479,7 @@ var CONTEXT_PATHS = [
|
|
|
4360
4479
|
async function existingEvidence(rootDir, candidates) {
|
|
4361
4480
|
const evidence = await Promise.all(
|
|
4362
4481
|
candidates.map(
|
|
4363
|
-
async ([relativePath, label]) => await fileExists(
|
|
4482
|
+
async ([relativePath, label]) => await fileExists(path19.join(rootDir, relativePath)) ? { source: "file", value: `${relativePath}:${label}` } : void 0
|
|
4364
4483
|
)
|
|
4365
4484
|
);
|
|
4366
4485
|
return evidence.flatMap((item) => item ? [item] : []);
|
|
@@ -4394,7 +4513,7 @@ function isRepositoryPurpose(value) {
|
|
|
4394
4513
|
}
|
|
4395
4514
|
async function readConfirmedPurpose(rootDir) {
|
|
4396
4515
|
const configuration = await readJson(
|
|
4397
|
-
|
|
4516
|
+
path19.join(rootDir, REPOSITORY_PROFILE_REL)
|
|
4398
4517
|
);
|
|
4399
4518
|
const configuredPurpose = configuration?.purpose;
|
|
4400
4519
|
if (!configuredPurpose || !isRepositoryPurpose(configuredPurpose.value) || configuredPurpose.value === "unknown") {
|
|
@@ -4406,7 +4525,7 @@ async function readConfirmedPurpose(rootDir) {
|
|
|
4406
4525
|
value: configuredPurpose.value,
|
|
4407
4526
|
categories: categories.length > 0 ? categories : [configuredPurpose.value],
|
|
4408
4527
|
confidence: "high",
|
|
4409
|
-
evidence: evidence.length > 0 ? evidence : [{ source: "configuration", value:
|
|
4528
|
+
evidence: evidence.length > 0 ? evidence : [{ source: "configuration", value: `${REPOSITORY_PROFILE_REL}#purpose.value` }]
|
|
4410
4529
|
};
|
|
4411
4530
|
}
|
|
4412
4531
|
async function detectPurpose(rootDir, stack) {
|
|
@@ -4414,7 +4533,7 @@ async function detectPurpose(rootDir, stack) {
|
|
|
4414
4533
|
if (confirmed) return confirmed;
|
|
4415
4534
|
const entries = await listDirectory(rootDir);
|
|
4416
4535
|
const lowerEntries = entries.map((entry) => entry.toLowerCase());
|
|
4417
|
-
const packageJson = await readJson(
|
|
4536
|
+
const packageJson = await readJson(path19.join(rootDir, "package.json"));
|
|
4418
4537
|
const categories = [];
|
|
4419
4538
|
const evidence = [];
|
|
4420
4539
|
const add = (category, value2) => {
|
|
@@ -4454,16 +4573,16 @@ async function detectPurpose(rootDir, stack) {
|
|
|
4454
4573
|
}
|
|
4455
4574
|
async function detectAgentKit(rootDir) {
|
|
4456
4575
|
const manifestRelativePath = ".cursor/agent-kit.json";
|
|
4457
|
-
const manifestPath =
|
|
4576
|
+
const manifestPath = path19.join(rootDir, manifestRelativePath);
|
|
4458
4577
|
const installed = await fileExists(manifestPath);
|
|
4459
4578
|
const manifest = installed ? await readJson(manifestPath) : null;
|
|
4460
4579
|
return {
|
|
4461
4580
|
installed,
|
|
4462
4581
|
manifestPath: installed ? manifestRelativePath : void 0,
|
|
4463
4582
|
version: manifest?.version,
|
|
4464
|
-
hasPlans: await fileExists(
|
|
4465
|
-
hasHandoff: await fileExists(
|
|
4466
|
-
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"))
|
|
4467
4586
|
};
|
|
4468
4587
|
}
|
|
4469
4588
|
var REQUIRED_SECRET_PATTERNS = [
|
|
@@ -4484,9 +4603,9 @@ var KIT_OWNED_IGNORE_PATTERNS = [
|
|
|
4484
4603
|
".cursor/context/mission-timing.json"
|
|
4485
4604
|
];
|
|
4486
4605
|
async function detectSafety(rootDir, trackedFiles) {
|
|
4487
|
-
const gitignorePath =
|
|
4606
|
+
const gitignorePath = path19.join(rootDir, ".gitignore");
|
|
4488
4607
|
const hasGitignore = await fileExists(gitignorePath);
|
|
4489
|
-
const gitignore = hasGitignore ? await
|
|
4608
|
+
const gitignore = hasGitignore ? await readFile12(gitignorePath, "utf8") : "";
|
|
4490
4609
|
const lines = gitignore.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
4491
4610
|
const ignoredSecretPatterns = REQUIRED_SECRET_PATTERNS.filter(
|
|
4492
4611
|
(pattern) => lines.includes(pattern)
|
|
@@ -4495,7 +4614,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
4495
4614
|
(file) => /(^|\/)(\.env(\..+)?|.*\.(key|pem|p12|pfx)|.*credentials.*\.json)$/i.test(file)
|
|
4496
4615
|
);
|
|
4497
4616
|
const hookPaths = [".husky", ".git/hooks/pre-commit", "git-hooks/pre-commit"];
|
|
4498
|
-
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);
|
|
4499
4618
|
const guardCandidates = [
|
|
4500
4619
|
".husky/pre-commit",
|
|
4501
4620
|
".husky/pre-push",
|
|
@@ -4504,7 +4623,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
4504
4623
|
];
|
|
4505
4624
|
const guardContents = await Promise.all(
|
|
4506
4625
|
guardCandidates.map(
|
|
4507
|
-
async (item) => await fileExists(
|
|
4626
|
+
async (item) => await fileExists(path19.join(rootDir, item)) ? readFile12(path19.join(rootDir, item), "utf8") : ""
|
|
4508
4627
|
)
|
|
4509
4628
|
);
|
|
4510
4629
|
return {
|
|
@@ -4522,11 +4641,11 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
4522
4641
|
}
|
|
4523
4642
|
|
|
4524
4643
|
// src/scanner/scan.ts
|
|
4525
|
-
import
|
|
4644
|
+
import path25 from "path";
|
|
4526
4645
|
|
|
4527
4646
|
// src/scanner/detect-git.ts
|
|
4528
4647
|
import { execFile as execFile4 } from "child_process";
|
|
4529
|
-
import
|
|
4648
|
+
import path20 from "path";
|
|
4530
4649
|
import { promisify as promisify4 } from "util";
|
|
4531
4650
|
var exec = promisify4(execFile4);
|
|
4532
4651
|
function remoteHostname(remoteUrl) {
|
|
@@ -4550,7 +4669,7 @@ function sanitizeRemoteUrl(remoteUrl) {
|
|
|
4550
4669
|
}
|
|
4551
4670
|
async function detectProvider(rootDir, remoteUrl) {
|
|
4552
4671
|
const configuration = await readJson(
|
|
4553
|
-
|
|
4672
|
+
path20.join(rootDir, REPOSITORY_PROFILE_REL)
|
|
4554
4673
|
);
|
|
4555
4674
|
const configuredProvider = configuration?.git?.provider;
|
|
4556
4675
|
if (configuredProvider) {
|
|
@@ -4559,7 +4678,7 @@ async function detectProvider(rootDir, remoteUrl) {
|
|
|
4559
4678
|
provider: configuredProvider,
|
|
4560
4679
|
providerKind: configuredKind,
|
|
4561
4680
|
confidence: "high",
|
|
4562
|
-
evidence: [{ source: "configuration", value:
|
|
4681
|
+
evidence: [{ source: "configuration", value: `${REPOSITORY_PROFILE_REL}#git.provider` }]
|
|
4563
4682
|
};
|
|
4564
4683
|
}
|
|
4565
4684
|
if (!remoteUrl) {
|
|
@@ -4607,7 +4726,7 @@ async function detectProvider(rootDir, remoteUrl) {
|
|
|
4607
4726
|
evidence: remoteEvidence
|
|
4608
4727
|
};
|
|
4609
4728
|
}
|
|
4610
|
-
if (await fileExists(
|
|
4729
|
+
if (await fileExists(path20.join(rootDir, ".gitlab-ci.yml"))) {
|
|
4611
4730
|
return {
|
|
4612
4731
|
provider: "gitlab",
|
|
4613
4732
|
providerKind: "gitlab-self-hosted",
|
|
@@ -4697,11 +4816,11 @@ async function detectGit(rootDir) {
|
|
|
4697
4816
|
}
|
|
4698
4817
|
|
|
4699
4818
|
// src/scanner/detect-ide.ts
|
|
4700
|
-
import
|
|
4819
|
+
import path21 from "path";
|
|
4701
4820
|
async function detectIde(rootDir) {
|
|
4702
|
-
const hasCursor = await fileExists(
|
|
4703
|
-
const hasVSCode = await fileExists(
|
|
4704
|
-
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"));
|
|
4705
4824
|
if (hasCursor) return { ide: "cursor", plan: "cursor-pro" };
|
|
4706
4825
|
if (hasVSCode) return { ide: "vscode", plan: "vscode-pro" };
|
|
4707
4826
|
if (hasWindsurf) return { ide: "windsurf", plan: "windsurf" };
|
|
@@ -4709,8 +4828,8 @@ async function detectIde(rootDir) {
|
|
|
4709
4828
|
}
|
|
4710
4829
|
|
|
4711
4830
|
// src/scanner/detect-infra.ts
|
|
4712
|
-
import { readFile as
|
|
4713
|
-
import
|
|
4831
|
+
import { readFile as readFile13, readdir as readdir5 } from "fs/promises";
|
|
4832
|
+
import path22 from "path";
|
|
4714
4833
|
|
|
4715
4834
|
// src/types.ts
|
|
4716
4835
|
var GIT_PLATFORM_META = {
|
|
@@ -4790,7 +4909,7 @@ function extractRunCommands(yaml) {
|
|
|
4790
4909
|
return Array.from(new Set(commands));
|
|
4791
4910
|
}
|
|
4792
4911
|
async function detectCiRunCommands(rootDir) {
|
|
4793
|
-
const workflowsDir =
|
|
4912
|
+
const workflowsDir = path22.join(rootDir, ".github", "workflows");
|
|
4794
4913
|
if (!await fileExists(workflowsDir)) return [];
|
|
4795
4914
|
let entries;
|
|
4796
4915
|
try {
|
|
@@ -4802,7 +4921,7 @@ async function detectCiRunCommands(rootDir) {
|
|
|
4802
4921
|
const perFile = await Promise.all(
|
|
4803
4922
|
workflowFiles.map(async (name) => {
|
|
4804
4923
|
try {
|
|
4805
|
-
return extractRunCommands(await
|
|
4924
|
+
return extractRunCommands(await readFile13(path22.join(workflowsDir, name), "utf8"));
|
|
4806
4925
|
} catch {
|
|
4807
4926
|
return [];
|
|
4808
4927
|
}
|
|
@@ -4811,12 +4930,12 @@ async function detectCiRunCommands(rootDir) {
|
|
|
4811
4930
|
return Array.from(new Set(perFile.flat()));
|
|
4812
4931
|
}
|
|
4813
4932
|
async function detectInfra(rootDir) {
|
|
4814
|
-
const docker = await fileExists(
|
|
4815
|
-
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"));
|
|
4816
4935
|
let ci = "none";
|
|
4817
4936
|
const ciFiles = [];
|
|
4818
4937
|
for (const [platform, filePath] of Object.entries(CI_PLATFORM_FILES)) {
|
|
4819
|
-
if (await fileExists(
|
|
4938
|
+
if (await fileExists(path22.join(rootDir, filePath))) {
|
|
4820
4939
|
if (ci === "none") ci = platform;
|
|
4821
4940
|
ciFiles.push(filePath);
|
|
4822
4941
|
}
|
|
@@ -4841,12 +4960,12 @@ async function detectInfra(rootDir) {
|
|
|
4841
4960
|
];
|
|
4842
4961
|
const infrastructureFiles = (await Promise.all(
|
|
4843
4962
|
infrastructureCandidates.map(
|
|
4844
|
-
async (file) => await fileExists(
|
|
4963
|
+
async (file) => await fileExists(path22.join(rootDir, file)) ? file : void 0
|
|
4845
4964
|
)
|
|
4846
4965
|
)).filter((file) => file !== void 0);
|
|
4847
4966
|
const deploymentFiles = (await Promise.all(
|
|
4848
4967
|
deploymentCandidates.map(
|
|
4849
|
-
async (file) => await fileExists(
|
|
4968
|
+
async (file) => await fileExists(path22.join(rootDir, file)) ? file : void 0
|
|
4850
4969
|
)
|
|
4851
4970
|
)).filter((file) => file !== void 0);
|
|
4852
4971
|
const ciRunCommands = ci === "github-actions" ? await detectCiRunCommands(rootDir) : [];
|
|
@@ -4854,18 +4973,18 @@ async function detectInfra(rootDir) {
|
|
|
4854
4973
|
}
|
|
4855
4974
|
|
|
4856
4975
|
// src/scanner/detect-services.ts
|
|
4857
|
-
import { readFile as
|
|
4858
|
-
import
|
|
4976
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
4977
|
+
import path23 from "path";
|
|
4859
4978
|
async function detectProjectManagement(rootDir) {
|
|
4860
4979
|
const tools = [];
|
|
4861
4980
|
const mcpConfigPaths = [
|
|
4862
|
-
|
|
4863
|
-
|
|
4981
|
+
path23.join(rootDir, ".cursor", "mcp.json"),
|
|
4982
|
+
path23.join(rootDir, "mcp.json")
|
|
4864
4983
|
];
|
|
4865
4984
|
for (const configPath of mcpConfigPaths) {
|
|
4866
4985
|
if (!await fileExists(configPath)) continue;
|
|
4867
4986
|
try {
|
|
4868
|
-
const raw = await
|
|
4987
|
+
const raw = await readFile14(configPath, "utf8");
|
|
4869
4988
|
const lower = raw.toLowerCase();
|
|
4870
4989
|
if (lower.includes("clickup")) tools.push("clickup");
|
|
4871
4990
|
if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
|
|
@@ -4876,20 +4995,20 @@ async function detectProjectManagement(rootDir) {
|
|
|
4876
4995
|
} catch {
|
|
4877
4996
|
}
|
|
4878
4997
|
}
|
|
4879
|
-
if (await fileExists(
|
|
4998
|
+
if (await fileExists(path23.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
|
|
4880
4999
|
tools.push("github-issues");
|
|
4881
5000
|
}
|
|
4882
|
-
if (await fileExists(
|
|
5001
|
+
if (await fileExists(path23.join(rootDir, ".github", "projects"))) {
|
|
4883
5002
|
tools.push("github-projects");
|
|
4884
5003
|
}
|
|
4885
5004
|
return [...new Set(tools)];
|
|
4886
5005
|
}
|
|
4887
5006
|
async function detectServices(rootDir) {
|
|
4888
|
-
const hasPrisma = await fileExists(
|
|
4889
|
-
const hasSequelize = await fileExists(
|
|
4890
|
-
const hasDrizzle = await fileExists(
|
|
4891
|
-
const hasKnex = await fileExists(
|
|
4892
|
-
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"));
|
|
4893
5012
|
const database = hasPrisma || hasSequelize || hasDrizzle || hasKnex || hasTypeorm ? "postgresql" : void 0;
|
|
4894
5013
|
const orm = hasPrisma ? "prisma" : hasDrizzle ? "drizzle" : hasSequelize ? "sequelize" : hasKnex ? "knex" : hasTypeorm ? "typeorm" : void 0;
|
|
4895
5014
|
const projectManagement = await detectProjectManagement(rootDir);
|
|
@@ -4901,8 +5020,8 @@ async function detectServices(rootDir) {
|
|
|
4901
5020
|
}
|
|
4902
5021
|
|
|
4903
5022
|
// src/scanner/detect-stack.ts
|
|
4904
|
-
import { readFile as
|
|
4905
|
-
import
|
|
5023
|
+
import { readFile as readFile15, readdir as readdir6 } from "fs/promises";
|
|
5024
|
+
import path24 from "path";
|
|
4906
5025
|
var PROJECT_MARKERS = [
|
|
4907
5026
|
"package.json",
|
|
4908
5027
|
"requirements.txt",
|
|
@@ -4928,7 +5047,7 @@ async function scanSubdirectoryMarkers(rootDir) {
|
|
|
4928
5047
|
const candidates = dirs.flatMap((dir) => PROJECT_MARKERS.map((marker) => ({ dir, marker })));
|
|
4929
5048
|
const found = await Promise.all(
|
|
4930
5049
|
candidates.map(async (candidate2) => {
|
|
4931
|
-
const exists2 = await fileExists(
|
|
5050
|
+
const exists2 = await fileExists(path24.join(rootDir, candidate2.dir, candidate2.marker));
|
|
4932
5051
|
return exists2 ? candidate2 : null;
|
|
4933
5052
|
})
|
|
4934
5053
|
);
|
|
@@ -4944,7 +5063,7 @@ var DART_VALIDATION_COMMANDS = [
|
|
|
4944
5063
|
async function detectDartFlavor(pubspecDir) {
|
|
4945
5064
|
let content = "";
|
|
4946
5065
|
try {
|
|
4947
|
-
content = await
|
|
5066
|
+
content = await readFile15(path24.join(pubspecDir, "pubspec.yaml"), "utf8");
|
|
4948
5067
|
} catch {
|
|
4949
5068
|
}
|
|
4950
5069
|
return { isFlutter: isFlutterPubspec(content) };
|
|
@@ -4957,7 +5076,7 @@ async function detectStackFromSubdirHits(rootDir, hits) {
|
|
|
4957
5076
|
const dartHits = hits.filter((hit) => hit.marker === "pubspec.yaml");
|
|
4958
5077
|
if (dartHits.length > 0) {
|
|
4959
5078
|
const flavors = await Promise.all(
|
|
4960
|
-
dartHits.map((hit) => detectDartFlavor(
|
|
5079
|
+
dartHits.map((hit) => detectDartFlavor(path24.join(rootDir, hit.dir)))
|
|
4961
5080
|
);
|
|
4962
5081
|
const testCommands = Array.from(
|
|
4963
5082
|
new Set(flavors.map((flavor) => flavor.isFlutter ? "flutter test" : "dart test"))
|
|
@@ -4974,8 +5093,8 @@ async function detectStackFromSubdirHits(rootDir, hits) {
|
|
|
4974
5093
|
}
|
|
4975
5094
|
const nodeHit = hits.find((hit) => hit.marker === "package.json");
|
|
4976
5095
|
if (nodeHit) {
|
|
4977
|
-
const subRoot =
|
|
4978
|
-
const packageJson = await readJson(
|
|
5096
|
+
const subRoot = path24.join(rootDir, nodeHit.dir);
|
|
5097
|
+
const packageJson = await readJson(path24.join(subRoot, "package.json")) ?? {};
|
|
4979
5098
|
const scripts = packageJson.scripts ?? {};
|
|
4980
5099
|
const packageManager = await detectPackageManager(subRoot, packageJson);
|
|
4981
5100
|
const commands = commandsForScripts(scripts, packageManager.packageManager);
|
|
@@ -5032,7 +5151,7 @@ function isDetectionEvidenceArray(value) {
|
|
|
5032
5151
|
}
|
|
5033
5152
|
async function readConfirmedStack(rootDir) {
|
|
5034
5153
|
const configuration = await readJson(
|
|
5035
|
-
|
|
5154
|
+
path24.join(rootDir, REPOSITORY_PROFILE_REL)
|
|
5036
5155
|
);
|
|
5037
5156
|
const configured = configuration?.stack;
|
|
5038
5157
|
const language = configured?.language;
|
|
@@ -5046,7 +5165,7 @@ async function readConfirmedStack(rootDir) {
|
|
|
5046
5165
|
const scripts = configured.scripts && typeof configured.scripts === "object" && !Array.isArray(configured.scripts) ? configured.scripts : void 0;
|
|
5047
5166
|
const packageManagerEvidence = isDetectionEvidenceArray(
|
|
5048
5167
|
configured.packageManagerEvidence
|
|
5049
|
-
) ? configured.packageManagerEvidence : [{ source: "configuration", value:
|
|
5168
|
+
) ? configured.packageManagerEvidence : [{ source: "configuration", value: `${REPOSITORY_PROFILE_REL}#stack.language` }];
|
|
5050
5169
|
return {
|
|
5051
5170
|
language,
|
|
5052
5171
|
framework: typeof configured.framework === "string" ? configured.framework : void 0,
|
|
@@ -5075,7 +5194,7 @@ async function detectPackageManager(rootDir, packageJson) {
|
|
|
5075
5194
|
};
|
|
5076
5195
|
}
|
|
5077
5196
|
for (const [lockfile, packageManager] of LOCKFILES) {
|
|
5078
|
-
if (await fileExists(
|
|
5197
|
+
if (await fileExists(path24.join(rootDir, lockfile))) {
|
|
5079
5198
|
return {
|
|
5080
5199
|
packageManager,
|
|
5081
5200
|
evidence: [{ source: "file", value: lockfile }]
|
|
@@ -5094,33 +5213,33 @@ function commandsForScripts(scripts, packageManager) {
|
|
|
5094
5213
|
async function detectStack(rootDir) {
|
|
5095
5214
|
const confirmed = await readConfirmedStack(rootDir);
|
|
5096
5215
|
if (confirmed) return confirmed;
|
|
5097
|
-
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);
|
|
5098
5217
|
if (!hasAnyProjectMarker) {
|
|
5099
5218
|
const subdirHits = await scanSubdirectoryMarkers(rootDir);
|
|
5100
5219
|
if (subdirHits.length > 0) {
|
|
5101
5220
|
return detectStackFromSubdirHits(rootDir, subdirHits);
|
|
5102
5221
|
}
|
|
5103
5222
|
}
|
|
5104
|
-
const hasPackageJson = await fileExists(
|
|
5223
|
+
const hasPackageJson = await fileExists(path24.join(rootDir, "package.json"));
|
|
5105
5224
|
if (hasPackageJson) {
|
|
5106
|
-
const packageJson = await readJson(
|
|
5225
|
+
const packageJson = await readJson(path24.join(rootDir, "package.json")) ?? {};
|
|
5107
5226
|
const scripts = packageJson.scripts ?? {};
|
|
5108
5227
|
const packageManager = await detectPackageManager(rootDir, packageJson);
|
|
5109
5228
|
const commands = commandsForScripts(scripts, packageManager.packageManager);
|
|
5110
|
-
const hasNextConfig = await fileExists(
|
|
5111
|
-
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"));
|
|
5112
5231
|
return {
|
|
5113
5232
|
language: "node",
|
|
5114
5233
|
framework: hasNextConfig ? "nextjs" : hasNestConfig ? "nestjs" : "node",
|
|
5115
5234
|
packageManager: packageManager.packageManager,
|
|
5116
5235
|
packageManagerEvidence: packageManager.evidence,
|
|
5117
5236
|
scripts,
|
|
5118
|
-
workspaces: packageJson.workspaces !== void 0 || await fileExists(
|
|
5237
|
+
workspaces: packageJson.workspaces !== void 0 || await fileExists(path24.join(rootDir, "pnpm-workspace.yaml")),
|
|
5119
5238
|
...commands,
|
|
5120
5239
|
hasProjectFiles: hasAnyProjectMarker
|
|
5121
5240
|
};
|
|
5122
5241
|
}
|
|
5123
|
-
if (await fileExists(
|
|
5242
|
+
if (await fileExists(path24.join(rootDir, "pyproject.toml"))) {
|
|
5124
5243
|
return {
|
|
5125
5244
|
language: "python",
|
|
5126
5245
|
framework: "python",
|
|
@@ -5130,7 +5249,7 @@ async function detectStack(rootDir) {
|
|
|
5130
5249
|
hasProjectFiles: hasAnyProjectMarker
|
|
5131
5250
|
};
|
|
5132
5251
|
}
|
|
5133
|
-
if (await fileExists(
|
|
5252
|
+
if (await fileExists(path24.join(rootDir, "go.mod"))) {
|
|
5134
5253
|
return {
|
|
5135
5254
|
language: "go",
|
|
5136
5255
|
framework: "go",
|
|
@@ -5140,7 +5259,7 @@ async function detectStack(rootDir) {
|
|
|
5140
5259
|
hasProjectFiles: hasAnyProjectMarker
|
|
5141
5260
|
};
|
|
5142
5261
|
}
|
|
5143
|
-
if (await fileExists(
|
|
5262
|
+
if (await fileExists(path24.join(rootDir, "Cargo.toml"))) {
|
|
5144
5263
|
return {
|
|
5145
5264
|
language: "rust",
|
|
5146
5265
|
framework: "rust",
|
|
@@ -5150,7 +5269,7 @@ async function detectStack(rootDir) {
|
|
|
5150
5269
|
hasProjectFiles: hasAnyProjectMarker
|
|
5151
5270
|
};
|
|
5152
5271
|
}
|
|
5153
|
-
if (await fileExists(
|
|
5272
|
+
if (await fileExists(path24.join(rootDir, "composer.json"))) {
|
|
5154
5273
|
return {
|
|
5155
5274
|
language: "php",
|
|
5156
5275
|
framework: "php",
|
|
@@ -5160,7 +5279,7 @@ async function detectStack(rootDir) {
|
|
|
5160
5279
|
hasProjectFiles: hasAnyProjectMarker
|
|
5161
5280
|
};
|
|
5162
5281
|
}
|
|
5163
|
-
if (await fileExists(
|
|
5282
|
+
if (await fileExists(path24.join(rootDir, "pubspec.yaml"))) {
|
|
5164
5283
|
const { isFlutter } = await detectDartFlavor(rootDir);
|
|
5165
5284
|
return {
|
|
5166
5285
|
language: "dart",
|
|
@@ -5194,7 +5313,7 @@ function isGreenfieldByEntries(entries) {
|
|
|
5194
5313
|
return meaningful.length === 0;
|
|
5195
5314
|
}
|
|
5196
5315
|
async function runScanner(rootDir) {
|
|
5197
|
-
const normalizedRoot =
|
|
5316
|
+
const normalizedRoot = path25.resolve(rootDir);
|
|
5198
5317
|
const entries = await listDirectory(normalizedRoot);
|
|
5199
5318
|
const stack = await detectStack(normalizedRoot);
|
|
5200
5319
|
const purpose = await detectPurpose(normalizedRoot, stack);
|
|
@@ -5232,8 +5351,7 @@ async function runScanner(rootDir) {
|
|
|
5232
5351
|
}
|
|
5233
5352
|
|
|
5234
5353
|
// src/scanner/safe-fixes.ts
|
|
5235
|
-
var
|
|
5236
|
-
var CONTEXT_CONFIG_RELATIVE_PATH = ".cursor/context/config.json";
|
|
5354
|
+
var CONTEXT_CONFIG_REL_POSIX = CONTEXT_CONFIG_REL.replace(/\\/g, "/");
|
|
5237
5355
|
var ESSENTIAL_DIRECTORIES = [
|
|
5238
5356
|
".cursor",
|
|
5239
5357
|
".cursor/context",
|
|
@@ -5418,7 +5536,7 @@ async function refreshRepositoryProfile(rootDir, options) {
|
|
|
5418
5536
|
generatedAt
|
|
5419
5537
|
});
|
|
5420
5538
|
const desiredProfile = createProfile(scan, report, generatedAt);
|
|
5421
|
-
const profilePath =
|
|
5539
|
+
const profilePath = path26.join(scan.rootDir, REPOSITORY_PROFILE_REL);
|
|
5422
5540
|
const existingProfile = await readJson(profilePath) ?? {};
|
|
5423
5541
|
const reconciled = reconcileFreshOverExisting(desiredProfile, existingProfile);
|
|
5424
5542
|
const meaningfulChange = !jsonEqual(
|
|
@@ -5441,7 +5559,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5441
5559
|
});
|
|
5442
5560
|
const changes = [];
|
|
5443
5561
|
for (const relativePath of ESSENTIAL_DIRECTORIES) {
|
|
5444
|
-
const absolutePath =
|
|
5562
|
+
const absolutePath = path26.join(beforeScan.rootDir, relativePath);
|
|
5445
5563
|
const exists2 = await fileExists(absolutePath);
|
|
5446
5564
|
if (!exists2 && !dryRun) await ensureDir(absolutePath);
|
|
5447
5565
|
recordChange(
|
|
@@ -5454,14 +5572,14 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5454
5572
|
);
|
|
5455
5573
|
}
|
|
5456
5574
|
const gitignoreRelativePath = ".gitignore";
|
|
5457
|
-
const gitignorePath =
|
|
5458
|
-
const existingGitignore = await fileExists(gitignorePath) ? await
|
|
5575
|
+
const gitignorePath = path26.join(beforeScan.rootDir, gitignoreRelativePath);
|
|
5576
|
+
const existingGitignore = await fileExists(gitignorePath) ? await readFile16(gitignorePath, "utf8") : "";
|
|
5459
5577
|
const gitignoreAfterSecrets = mergeSecretIgnores(existingGitignore);
|
|
5460
5578
|
const secretsChanged = gitignoreAfterSecrets !== existingGitignore;
|
|
5461
5579
|
const mergedGitignore = mergeKitOwnedIgnores(gitignoreAfterSecrets);
|
|
5462
5580
|
const gitignoreChanged = mergedGitignore !== existingGitignore;
|
|
5463
5581
|
const kitOwnedChanged = mergedGitignore !== gitignoreAfterSecrets;
|
|
5464
|
-
if (gitignoreChanged && !dryRun) await
|
|
5582
|
+
if (gitignoreChanged && !dryRun) await writeFile6(gitignorePath, mergedGitignore, "utf8");
|
|
5465
5583
|
recordChange(
|
|
5466
5584
|
changes,
|
|
5467
5585
|
"merge-secret-ignores",
|
|
@@ -5484,7 +5602,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5484
5602
|
kitOwnedChanged ? "kit-owned session/derived paths are missing" : "kit-owned session/derived paths are present"
|
|
5485
5603
|
)
|
|
5486
5604
|
);
|
|
5487
|
-
const profilePath =
|
|
5605
|
+
const profilePath = path26.join(beforeScan.rootDir, REPOSITORY_PROFILE_REL);
|
|
5488
5606
|
const existingProfile = await readJson(profilePath) ?? {};
|
|
5489
5607
|
const desiredProfile = createProfile(beforeScan, before, generatedAt);
|
|
5490
5608
|
const mergedProfile = mergeMissing(existingProfile, desiredProfile);
|
|
@@ -5493,11 +5611,11 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5493
5611
|
recordChange(
|
|
5494
5612
|
changes,
|
|
5495
5613
|
"merge-repository-profile",
|
|
5496
|
-
|
|
5614
|
+
REPOSITORY_PROFILE_REL,
|
|
5497
5615
|
profileChanged,
|
|
5498
5616
|
dryRun,
|
|
5499
5617
|
relativeEvidence(
|
|
5500
|
-
|
|
5618
|
+
REPOSITORY_PROFILE_REL,
|
|
5501
5619
|
profileChanged ? "missing scanner-derived facts" : "existing facts preserved"
|
|
5502
5620
|
)
|
|
5503
5621
|
);
|
|
@@ -5506,22 +5624,22 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5506
5624
|
generatorVersion: options.generatorVersion,
|
|
5507
5625
|
generatedAt
|
|
5508
5626
|
});
|
|
5509
|
-
const
|
|
5510
|
-
const existingContextConfig = await readJson(
|
|
5627
|
+
const configPath = contextConfigPath(beforeScan.rootDir);
|
|
5628
|
+
const existingContextConfig = await readJson(configPath) ?? {};
|
|
5511
5629
|
const onboarding = reconcileOnboardingState(evidenceReport, existingContextConfig, generatedAt);
|
|
5512
5630
|
const defaults = preferenceDefaults(onboarding, existingContextConfig.onboarded);
|
|
5513
5631
|
const mergedContextConfig = mergeMissing(existingContextConfig, defaults);
|
|
5514
5632
|
mergedContextConfig.onboarding = onboarding;
|
|
5515
5633
|
const contextConfigChanged = !jsonEqual(existingContextConfig, mergedContextConfig);
|
|
5516
|
-
if (contextConfigChanged && !dryRun) await writeJson(
|
|
5634
|
+
if (contextConfigChanged && !dryRun) await writeJson(configPath, mergedContextConfig);
|
|
5517
5635
|
recordChange(
|
|
5518
5636
|
changes,
|
|
5519
5637
|
"merge-onboarding-state",
|
|
5520
|
-
|
|
5638
|
+
CONTEXT_CONFIG_REL_POSIX,
|
|
5521
5639
|
contextConfigChanged,
|
|
5522
5640
|
dryRun,
|
|
5523
5641
|
relativeEvidence(
|
|
5524
|
-
|
|
5642
|
+
CONTEXT_CONFIG_REL_POSIX,
|
|
5525
5643
|
contextConfigChanged ? "missing preferences or onboarding state" : "state already merged"
|
|
5526
5644
|
)
|
|
5527
5645
|
);
|
|
@@ -5535,10 +5653,10 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
5535
5653
|
}
|
|
5536
5654
|
|
|
5537
5655
|
// src/scanner/snapshot.ts
|
|
5538
|
-
import
|
|
5656
|
+
import path27 from "path";
|
|
5539
5657
|
var READINESS_SNAPSHOT_RELATIVE_PATH = ".cursor/context/readiness.json";
|
|
5540
5658
|
async function writeReadinessSnapshot(rootDir, report) {
|
|
5541
|
-
const snapshotPath =
|
|
5659
|
+
const snapshotPath = path27.join(rootDir, READINESS_SNAPSHOT_RELATIVE_PATH);
|
|
5542
5660
|
await writeJson(snapshotPath, report);
|
|
5543
5661
|
return snapshotPath;
|
|
5544
5662
|
}
|
|
@@ -5730,7 +5848,7 @@ async function withCliProgress(label, fn, opts) {
|
|
|
5730
5848
|
// src/commands/doctor.ts
|
|
5731
5849
|
async function readDeferredEssentials(rootDir, report) {
|
|
5732
5850
|
const config = await readJson(
|
|
5733
|
-
|
|
5851
|
+
path28.join(rootDir, ".cursor", "context", "config.json")
|
|
5734
5852
|
);
|
|
5735
5853
|
const deferredItems = config?.onboarding?.deferredItems;
|
|
5736
5854
|
if (!Array.isArray(deferredItems)) return [];
|
|
@@ -5742,7 +5860,7 @@ async function readDeferredEssentials(rootDir, report) {
|
|
|
5742
5860
|
).map((item) => ({ checkId: item.checkId, reason: item.reason }));
|
|
5743
5861
|
}
|
|
5744
5862
|
async function runDoctor(cwd, options = {}) {
|
|
5745
|
-
const rootDir =
|
|
5863
|
+
const rootDir = path28.resolve(cwd);
|
|
5746
5864
|
const hooks = await assessHooksHealth(rootDir);
|
|
5747
5865
|
const env = await assessEnvironment();
|
|
5748
5866
|
if (options.fixSafe) {
|
|
@@ -6270,8 +6388,8 @@ var guardCommand = defineCommand8({
|
|
|
6270
6388
|
|
|
6271
6389
|
// src/commands/handoff.ts
|
|
6272
6390
|
import { spawn as spawn4 } from "child_process";
|
|
6273
|
-
import { appendFile, readFile as
|
|
6274
|
-
import
|
|
6391
|
+
import { appendFile, readFile as readFile17, readdir as readdir7, writeFile as writeFile7 } from "fs/promises";
|
|
6392
|
+
import path29 from "path";
|
|
6275
6393
|
import { defineCommand as defineCommand9 } from "citty";
|
|
6276
6394
|
|
|
6277
6395
|
// src/invariants/handoff-prune.ts
|
|
@@ -6412,12 +6530,12 @@ function pruneArchiveTimestamp(now2 = /* @__PURE__ */ new Date()) {
|
|
|
6412
6530
|
return `${now2.getFullYear()}-${pad(now2.getMonth() + 1)}-${pad(now2.getDate())}-${pad(now2.getHours())}${pad(now2.getMinutes())}`;
|
|
6413
6531
|
}
|
|
6414
6532
|
async function runPrune(cwd, keep) {
|
|
6415
|
-
const handoffPath =
|
|
6533
|
+
const handoffPath = path29.join(cwd, ".cursor", "HANDOFF.md");
|
|
6416
6534
|
if (!await fileExists(handoffPath)) {
|
|
6417
6535
|
logger.warn("No .cursor/HANDOFF.md to prune.");
|
|
6418
6536
|
return;
|
|
6419
6537
|
}
|
|
6420
|
-
const original = await
|
|
6538
|
+
const original = await readFile17(handoffPath, "utf8");
|
|
6421
6539
|
const result = pruneHandoffText(original, keep);
|
|
6422
6540
|
if (!result.ok) {
|
|
6423
6541
|
logger.warn(`Refusing to prune: ${result.reason}`);
|
|
@@ -6430,9 +6548,9 @@ async function runPrune(cwd, keep) {
|
|
|
6430
6548
|
);
|
|
6431
6549
|
return;
|
|
6432
6550
|
}
|
|
6433
|
-
const archiveDir =
|
|
6551
|
+
const archiveDir = path29.join(cwd, ".cursor", "context", "archive");
|
|
6434
6552
|
await ensureDir(archiveDir);
|
|
6435
|
-
const archivePath =
|
|
6553
|
+
const archivePath = path29.join(archiveDir, `handoff-${pruneArchiveTimestamp()}.md`);
|
|
6436
6554
|
const header = [
|
|
6437
6555
|
`<!-- Pruned from .cursor/HANDOFF.md at ${(/* @__PURE__ */ new Date()).toISOString()} -->`,
|
|
6438
6556
|
`<!-- ${result.archived.length} narrative ${result.archived.length === 1 ? "entry" : "entries"} archived, keep=${keep} -->`,
|
|
@@ -6444,11 +6562,11 @@ async function runPrune(cwd, keep) {
|
|
|
6444
6562
|
await appendFile(archivePath, `
|
|
6445
6563
|
${header}${body}`, "utf8");
|
|
6446
6564
|
} else {
|
|
6447
|
-
await
|
|
6565
|
+
await writeFile7(archivePath, `${header}${body}`, "utf8");
|
|
6448
6566
|
}
|
|
6449
|
-
await
|
|
6567
|
+
await writeFile7(handoffPath, result.text ?? original, "utf8");
|
|
6450
6568
|
logger.success(
|
|
6451
|
-
`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)}.`
|
|
6452
6570
|
);
|
|
6453
6571
|
}
|
|
6454
6572
|
function parsePlanFrontmatter(raw) {
|
|
@@ -6469,19 +6587,19 @@ async function findActivePlan(plansDir) {
|
|
|
6469
6587
|
if (!await fileExists(plansDir)) return null;
|
|
6470
6588
|
const files = (await readdir7(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
|
|
6471
6589
|
for (const file of files) {
|
|
6472
|
-
const raw = await
|
|
6590
|
+
const raw = await readFile17(path29.join(plansDir, file), "utf8");
|
|
6473
6591
|
const fm = parsePlanFrontmatter(raw);
|
|
6474
6592
|
if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
|
|
6475
6593
|
return { file, raw };
|
|
6476
6594
|
}
|
|
6477
6595
|
}
|
|
6478
|
-
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;
|
|
6479
6597
|
}
|
|
6480
6598
|
function now() {
|
|
6481
6599
|
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
|
|
6482
6600
|
}
|
|
6483
6601
|
async function loadProfile(rootDir) {
|
|
6484
|
-
const configPath =
|
|
6602
|
+
const configPath = path29.join(rootDir, ".cursor", "agent-kit.config.json");
|
|
6485
6603
|
try {
|
|
6486
6604
|
return await readJson(configPath);
|
|
6487
6605
|
} catch {
|
|
@@ -6596,15 +6714,15 @@ var handoffCommand = defineCommand9({
|
|
|
6596
6714
|
return;
|
|
6597
6715
|
}
|
|
6598
6716
|
const profile = await loadProfile(args.cwd);
|
|
6599
|
-
const plansDir =
|
|
6600
|
-
const handoffPath =
|
|
6717
|
+
const plansDir = path29.join(args.cwd, ".cursor", "plans");
|
|
6718
|
+
const handoffPath = path29.join(args.cwd, ".cursor", "HANDOFF.md");
|
|
6601
6719
|
const plan = await findActivePlan(plansDir);
|
|
6602
6720
|
if (plan) {
|
|
6603
6721
|
const fm = parsePlanFrontmatter(plan.raw);
|
|
6604
6722
|
if (fm) {
|
|
6605
|
-
await ensureDir(
|
|
6723
|
+
await ensureDir(path29.join(args.cwd, ".cursor"));
|
|
6606
6724
|
const content = buildHandoff(plan.file, fm, profile);
|
|
6607
|
-
await
|
|
6725
|
+
await writeFile7(handoffPath, content, "utf8");
|
|
6608
6726
|
logger.success("HANDOFF.md updated: .cursor/HANDOFF.md");
|
|
6609
6727
|
logger.info(
|
|
6610
6728
|
`Plan: ${plan.file} (${fm.todos?.filter((t) => t.status === "completed").length}/${fm.todos?.length} completed)`
|
|
@@ -6622,7 +6740,7 @@ var handoffCommand = defineCommand9({
|
|
|
6622
6740
|
}
|
|
6623
6741
|
logger.warn(`Plan ${plan.file} without valid frontmatter; trying legacy flow.`);
|
|
6624
6742
|
}
|
|
6625
|
-
const scriptPath =
|
|
6743
|
+
const scriptPath = path29.join(args.cwd, "cursor-handoff");
|
|
6626
6744
|
if (!await fileExists(scriptPath)) {
|
|
6627
6745
|
printV3Guidance();
|
|
6628
6746
|
return;
|
|
@@ -6647,7 +6765,7 @@ var handoffCommand = defineCommand9({
|
|
|
6647
6765
|
});
|
|
6648
6766
|
|
|
6649
6767
|
// src/commands/hook.ts
|
|
6650
|
-
import
|
|
6768
|
+
import path32 from "path";
|
|
6651
6769
|
import { defineCommand as defineCommand10 } from "citty";
|
|
6652
6770
|
|
|
6653
6771
|
// src/hooks/format-session-start.ts
|
|
@@ -6673,8 +6791,8 @@ function buildPreCompactUserMessage(payload = {}) {
|
|
|
6673
6791
|
|
|
6674
6792
|
// src/hooks/session-start.ts
|
|
6675
6793
|
import { spawn as spawn5 } from "child_process";
|
|
6676
|
-
import {
|
|
6677
|
-
import
|
|
6794
|
+
import { readFile as readFile19, stat as stat4 } from "fs/promises";
|
|
6795
|
+
import path31 from "path";
|
|
6678
6796
|
|
|
6679
6797
|
// src/invariants/handoff-schema.ts
|
|
6680
6798
|
var MACHINE_LIST_CHECKS = [
|
|
@@ -6716,8 +6834,8 @@ function validateHandoffText(text) {
|
|
|
6716
6834
|
}
|
|
6717
6835
|
|
|
6718
6836
|
// src/plan-index/plan-index.ts
|
|
6719
|
-
import { readFile as
|
|
6720
|
-
import
|
|
6837
|
+
import { readFile as readFile18, writeFile as writeFile8 } from "fs/promises";
|
|
6838
|
+
import path30 from "path";
|
|
6721
6839
|
var PLAN_INDEX_REL = ".cursor/context/plan-index.json";
|
|
6722
6840
|
var NONE = /^(none|n\/a|empty|nil)$/i;
|
|
6723
6841
|
var OPEN_STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress"]);
|
|
@@ -6725,14 +6843,14 @@ function defaultPlanIndexIo() {
|
|
|
6725
6843
|
return {
|
|
6726
6844
|
async readFile(absPath) {
|
|
6727
6845
|
try {
|
|
6728
|
-
return await
|
|
6846
|
+
return await readFile18(absPath, "utf8");
|
|
6729
6847
|
} catch {
|
|
6730
6848
|
return null;
|
|
6731
6849
|
}
|
|
6732
6850
|
},
|
|
6733
6851
|
async writeFile(absPath, body) {
|
|
6734
|
-
await ensureDir(
|
|
6735
|
-
await
|
|
6852
|
+
await ensureDir(path30.dirname(absPath));
|
|
6853
|
+
await writeFile8(absPath, body, "utf8");
|
|
6736
6854
|
}
|
|
6737
6855
|
};
|
|
6738
6856
|
}
|
|
@@ -6757,11 +6875,11 @@ function collectNamedPlanBasenames(named) {
|
|
|
6757
6875
|
return out;
|
|
6758
6876
|
}
|
|
6759
6877
|
function namedPlanCandidatePaths(root, basename) {
|
|
6760
|
-
const safe =
|
|
6878
|
+
const safe = path30.basename(basename);
|
|
6761
6879
|
if (!/^[A-Za-z0-9._-]+\.plan\.md$/i.test(safe)) return [];
|
|
6762
6880
|
return [
|
|
6763
|
-
|
|
6764
|
-
|
|
6881
|
+
path30.join(root, ".cursor", "plans", safe),
|
|
6882
|
+
path30.join(root, ".cursor", "plans", "archive", safe)
|
|
6765
6883
|
];
|
|
6766
6884
|
}
|
|
6767
6885
|
function classifyPlanRole(file, named, openTodos) {
|
|
@@ -6793,7 +6911,7 @@ function parsePlanFrontmatter2(raw) {
|
|
|
6793
6911
|
return { name, todos };
|
|
6794
6912
|
}
|
|
6795
6913
|
async function buildPlanIndex(root, io = defaultPlanIndexIo(), now2 = () => /* @__PURE__ */ new Date()) {
|
|
6796
|
-
const handoffPath =
|
|
6914
|
+
const handoffPath = path30.join(root, HANDOFF_REL);
|
|
6797
6915
|
const handoffText = await io.readFile(handoffPath) ?? "";
|
|
6798
6916
|
const named = extractHandoffNamedPlans(handoffText);
|
|
6799
6917
|
const plans = [];
|
|
@@ -6822,7 +6940,7 @@ async function buildPlanIndex(root, io = defaultPlanIndexIo(), now2 = () => /* @
|
|
|
6822
6940
|
}
|
|
6823
6941
|
async function writePlanIndex(root, io = defaultPlanIndexIo(), now2 = () => /* @__PURE__ */ new Date()) {
|
|
6824
6942
|
const index = await buildPlanIndex(root, io, now2);
|
|
6825
|
-
const dest =
|
|
6943
|
+
const dest = path30.join(root, PLAN_INDEX_REL);
|
|
6826
6944
|
const write = io.writeFile ?? defaultPlanIndexIo().writeFile;
|
|
6827
6945
|
if (write) {
|
|
6828
6946
|
await write(dest, `${JSON.stringify(index, null, 2)}
|
|
@@ -7034,11 +7152,11 @@ var HARD_RULES = `# Agent Kit session hard rules (manual mode default)
|
|
|
7034
7152
|
2. **Do not burn the window.** Never run an entire multi-phase plan in one conversation unless the user explicitly ran \`/run-plan\` (or a deprecated alias \`/run-plan-loop\` / \`/run-plan-orchestrated\`).
|
|
7035
7153
|
3. **Context questions are not optional.** If the user asks about context / contexto / window size, run the context-guardian protocol: warn, offer handoff, do NOT dismiss with "it's fine" and keep coding.
|
|
7036
7154
|
4. **Read HANDOFF first** when resuming. Do not restart the plan from scratch.
|
|
7037
|
-
5. **Git:** suggest \`/git-staging\` after a phase with a diff; never \`/git-prod\` without explicit confirmation.
|
|
7155
|
+
5. **Git:** suggest \`/git-staging\` after a phase with a diff; never \`/git-prod\` without explicit confirmation. A \`/run-plan-all\` queue confirm with Ship auth \`per-plan-release\` is that confirmation for one ship per completed plan.
|
|
7038
7156
|
6. **HITL slash commands win.** When waiting for confirmation on \`/git-staging\` or \`/git-prod\` (or similar), do not divert to continue-plan / phase-boundary chatter; stay on that routine until the user answers.
|
|
7039
7157
|
7. **\`/start-project\` is plan bootstrap, not execute.** Broad Intake Review first, then two gates: (A) single composite question (with active plan: backlog+write / park+write / modify / cancel; without: write / write+backlog (\`Write plan and add to backlog\`) / modify / cancel): approve/write the plan file only, (B) approve the first unit. Goal text in the same message is NOT permission to edit product files. Never "create plan and start Phase 1" in one turn. If HANDOFF already has an active plan, disposition is merged into Gate A composite options; never park silently. Gates use Ask questions per \`.cursor/skills/core/hitl-gates/SKILL.md\` and \`.cursor/rules/hitl-ask-questions.mdc\`. Fallback: numbered list is path 1.
|
|
7040
7158
|
8. **\`/continue-plan\` waits for yes.** Summarize next \`[to-do-id]\`, then stop until the user confirms before editing.
|
|
7041
|
-
9. **\`/run-plan-all\` is a pure orchestrator.** After the confirm queue Ask, dispatch one Task subagent per plan (run the \`/run-plan\` tick contract inside it)
|
|
7159
|
+
9. **\`/run-plan-all\` is a pure orchestrator.** After the confirm queue Ask, dispatch one Task subagent per plan (run the \`/run-plan\` tick contract inside it). Do not implement that plan's to-dos in the orchestrator window. When Ship auth is \`per-plan-release\`, this window ships one release after each completed plan and waits until it is Done before the next plan. \`Run plans only\` does not ship. A red public sync stops the queue.
|
|
7042
7160
|
10. **Backlog CRUD never activates.** \`/backlog-add\` enqueues (Broad Intake + write Ask + plan file + HANDOFF Backlog) without park, activate, or Gate B. \`/backlog-edit\` / \`/backlog-delete\` / \`/backlog-cancel\` require Ask confirm before mutate; delete archives from Backlog, cancel is soft in place. No Field Report cards for routine backlog CRUD.
|
|
7043
7161
|
11. **HANDOFF machine fields are bullet fields, not \`##\` headings.** Mission Control parses \`- **Plan:**\`, \`- **Backlog plans:**\`, \`- **Parked plans:**\`, \`- **Run queue:**\` (etc.). Canonical Plan: \`- **Plan:** \\\`name.plan.md\\\`\` or \`none\`. Nested backlog/parked rows: \`- \\\`other.plan.md\\\`\`. Never invent \`## Backlog plans\` / \`## Parked plans\` / \`## Run queue\` headings in place of those fields (Checklist / Current mission go empty or idle).
|
|
7044
7162
|
12. **Plans inventory is index + HANDOFF.** \`/continue-plan\`, \`/run-plan-all\`, and Broad Intake read \`.cursor/context/plan-index.json\` and \`.cursor/HANDOFF.md\` only. Do not glob \`.cursor/plans/*.plan.md\`. Named single-file reads of a known basename remain OK. Delegation stays (ADR \`decisions/2026-07-26_command-orchestration-delegation-pattern.md\`).`;
|
|
@@ -7061,19 +7179,11 @@ var NONE_PLACEHOLDERS = /* @__PURE__ */ new Set(["none", "n/a", "empty", "nil"])
|
|
|
7061
7179
|
var CURSOR_AWARENESS_SPAWN_TIMEOUT_MS = CHANGELOG_FETCH_TIMEOUT_MS + 3e3;
|
|
7062
7180
|
async function readFull(filePath) {
|
|
7063
7181
|
try {
|
|
7064
|
-
return await
|
|
7182
|
+
return await readFile19(filePath, "utf8");
|
|
7065
7183
|
} catch {
|
|
7066
7184
|
return "";
|
|
7067
7185
|
}
|
|
7068
7186
|
}
|
|
7069
|
-
async function fileExists2(p) {
|
|
7070
|
-
try {
|
|
7071
|
-
await access7(p);
|
|
7072
|
-
return true;
|
|
7073
|
-
} catch {
|
|
7074
|
-
return false;
|
|
7075
|
-
}
|
|
7076
|
-
}
|
|
7077
7187
|
function isMarkdownTableSeparator(line) {
|
|
7078
7188
|
const stripped = line.trim();
|
|
7079
7189
|
if (!stripped.startsWith("|")) return false;
|
|
@@ -7135,8 +7245,7 @@ function extractUnprocessedDogfoodLine(line) {
|
|
|
7135
7245
|
return raw;
|
|
7136
7246
|
}
|
|
7137
7247
|
async function l0Present(root) {
|
|
7138
|
-
|
|
7139
|
-
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"));
|
|
7140
7249
|
}
|
|
7141
7250
|
function checkLabelAndRecommendation(check2) {
|
|
7142
7251
|
const checkId = check2.id;
|
|
@@ -7175,10 +7284,10 @@ function unresolvedReadinessChecks(data) {
|
|
|
7175
7284
|
return { essential, nonessential };
|
|
7176
7285
|
}
|
|
7177
7286
|
async function readinessSection(root) {
|
|
7178
|
-
const snapshotPath =
|
|
7287
|
+
const snapshotPath = path31.join(root, READINESS_SNAPSHOT_RELATIVE_PATH);
|
|
7179
7288
|
let data;
|
|
7180
7289
|
try {
|
|
7181
|
-
data = JSON.parse(await
|
|
7290
|
+
data = JSON.parse(await readFile19(snapshotPath, "utf8"));
|
|
7182
7291
|
} catch {
|
|
7183
7292
|
return null;
|
|
7184
7293
|
}
|
|
@@ -7209,13 +7318,13 @@ Optional readiness item: \`${first.id}\`. ${first.recommendation} This does not
|
|
|
7209
7318
|
}
|
|
7210
7319
|
async function dogfoodInboxSection(root) {
|
|
7211
7320
|
const candidateReadmes = [
|
|
7212
|
-
|
|
7213
|
-
|
|
7321
|
+
path31.join(root, "dogfood", "README.md"),
|
|
7322
|
+
path31.join(root, ".cursor", "dogfood", "README.md")
|
|
7214
7323
|
];
|
|
7215
7324
|
for (const readme of candidateReadmes) {
|
|
7216
|
-
if (!await
|
|
7325
|
+
if (!await fileExists(readme)) continue;
|
|
7217
7326
|
try {
|
|
7218
|
-
const text = await
|
|
7327
|
+
const text = await readFile19(readme, "utf8");
|
|
7219
7328
|
if (parseUnprocessedDogfoodItems(text).length) return DOGFOOD_INBOX_HINT;
|
|
7220
7329
|
} catch {
|
|
7221
7330
|
}
|
|
@@ -7223,18 +7332,13 @@ async function dogfoodInboxSection(root) {
|
|
|
7223
7332
|
return null;
|
|
7224
7333
|
}
|
|
7225
7334
|
async function loadUpdateCheckPrefs(root) {
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
const uc = data.updateCheck;
|
|
7231
|
-
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
7232
|
-
return null;
|
|
7233
|
-
}
|
|
7234
|
-
return uc;
|
|
7235
|
-
} 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) {
|
|
7236
7339
|
return null;
|
|
7237
7340
|
}
|
|
7341
|
+
return uc;
|
|
7238
7342
|
}
|
|
7239
7343
|
function runUpdateCheckJson(root) {
|
|
7240
7344
|
return new Promise((resolve3) => {
|
|
@@ -7301,18 +7405,13 @@ async function updateCheckSection(root) {
|
|
|
7301
7405
|
return UPDATE_CHECK_NUDGE.replace("{installed}", installed).replace("{latest}", latest);
|
|
7302
7406
|
}
|
|
7303
7407
|
async function loadCursorUpdateCheckPrefs(root) {
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
const uc = data.cursorUpdateCheck;
|
|
7309
|
-
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
7310
|
-
return null;
|
|
7311
|
-
}
|
|
7312
|
-
return uc;
|
|
7313
|
-
} 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) {
|
|
7314
7412
|
return null;
|
|
7315
7413
|
}
|
|
7414
|
+
return uc;
|
|
7316
7415
|
}
|
|
7317
7416
|
function runCursorAwarenessJson(root) {
|
|
7318
7417
|
return new Promise((resolve3) => {
|
|
@@ -7447,7 +7546,7 @@ function parseScreenDetachedAuditSessions(listing) {
|
|
|
7447
7546
|
const [, pid, name, marker] = m;
|
|
7448
7547
|
if (!name || !name.startsWith(AUDIT_SESSION_NS_PREFIX)) continue;
|
|
7449
7548
|
if (marker && /[Aa]ttached/.test(marker)) continue;
|
|
7450
|
-
entries.push({ name, socketPath: sockdir ?
|
|
7549
|
+
entries.push({ name, socketPath: sockdir ? path31.join(sockdir, `${pid}.${name}`) : null });
|
|
7451
7550
|
}
|
|
7452
7551
|
return entries;
|
|
7453
7552
|
}
|
|
@@ -7495,8 +7594,8 @@ ${sessions.length} detached \`agent-kit-audit-*\` PTY ${noun} on this host (${ol
|
|
|
7495
7594
|
}
|
|
7496
7595
|
}
|
|
7497
7596
|
async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
7498
|
-
const root =
|
|
7499
|
-
const handoffPath =
|
|
7597
|
+
const root = path31.resolve(rootDir);
|
|
7598
|
+
const handoffPath = path31.join(root, HANDOFF_REL);
|
|
7500
7599
|
const handoffFull = await readFull(handoffPath);
|
|
7501
7600
|
const handoff = capHandoffExcerpt(handoffFull);
|
|
7502
7601
|
const parts = [HARD_RULES];
|
|
@@ -7563,7 +7662,7 @@ async function runSessionStartHook(cwd, formatArg, deps = {}) {
|
|
|
7563
7662
|
const readStdin = deps.readStdin ?? readStdinJson;
|
|
7564
7663
|
const buildContext = deps.buildContext ?? buildSessionStartAdditionalContext;
|
|
7565
7664
|
const payload = await readStdin();
|
|
7566
|
-
const root = resolveSessionRoot(payload,
|
|
7665
|
+
const root = resolveSessionRoot(payload, path32.resolve(cwd));
|
|
7567
7666
|
const out = await buildContext(root, payload);
|
|
7568
7667
|
return formatSessionStartOutput(out.additional_context, format);
|
|
7569
7668
|
} catch {
|
|
@@ -7616,12 +7715,12 @@ import { defineCommand as defineCommand12 } from "citty";
|
|
|
7616
7715
|
|
|
7617
7716
|
// src/lifecycle/path-cli.ts
|
|
7618
7717
|
import { spawn as spawn6 } from "child_process";
|
|
7619
|
-
import
|
|
7718
|
+
import path34 from "path";
|
|
7620
7719
|
|
|
7621
7720
|
// src/utils/terminal.ts
|
|
7622
7721
|
import { readdir as readdir8 } from "fs/promises";
|
|
7623
7722
|
import { homedir as homedir3 } from "os";
|
|
7624
|
-
import
|
|
7723
|
+
import path33 from "path";
|
|
7625
7724
|
import { confirm, isCancel } from "@clack/prompts";
|
|
7626
7725
|
function isNonInteractive() {
|
|
7627
7726
|
if (process.env.CI === "true" || process.env.CI === "1") return true;
|
|
@@ -7645,14 +7744,14 @@ async function findNestedRepoChildren(resolved) {
|
|
|
7645
7744
|
if (!entry.isDirectory()) continue;
|
|
7646
7745
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
7647
7746
|
scanned += 1;
|
|
7648
|
-
if (await fileExists(
|
|
7747
|
+
if (await fileExists(path33.join(resolved, entry.name, ".git"))) {
|
|
7649
7748
|
nested.push(entry.name);
|
|
7650
7749
|
}
|
|
7651
7750
|
}
|
|
7652
7751
|
return nested;
|
|
7653
7752
|
}
|
|
7654
7753
|
async function validateProjectRoot(resolved) {
|
|
7655
|
-
const home =
|
|
7754
|
+
const home = path33.resolve(homedir3());
|
|
7656
7755
|
if (resolved === "/" || resolved === home) {
|
|
7657
7756
|
return {
|
|
7658
7757
|
ok: false,
|
|
@@ -7660,12 +7759,12 @@ async function validateProjectRoot(resolved) {
|
|
|
7660
7759
|
recovery: "Change into a project directory and re-run. Agent Kit installs per project."
|
|
7661
7760
|
};
|
|
7662
7761
|
}
|
|
7663
|
-
const hasGit = await fileExists(
|
|
7664
|
-
const hasManifest = await fileExists(
|
|
7762
|
+
const hasGit = await fileExists(path33.join(resolved, ".git"));
|
|
7763
|
+
const hasManifest = await fileExists(path33.join(resolved, MANIFEST_RELATIVE_PATH));
|
|
7665
7764
|
if (!hasGit && !hasManifest) {
|
|
7666
7765
|
return {
|
|
7667
7766
|
ok: false,
|
|
7668
|
-
reason: `Refused ${resolved}: no .git and no
|
|
7767
|
+
reason: `Refused ${resolved}: no .git and no ${MANIFEST_RELATIVE_PATH}.`,
|
|
7669
7768
|
recovery: [
|
|
7670
7769
|
"Starting from an empty folder? Pick one of these:",
|
|
7671
7770
|
" 1. git init - then re-run. Recommended: readiness and the",
|
|
@@ -7694,7 +7793,7 @@ async function validateProjectRoot(resolved) {
|
|
|
7694
7793
|
return { ok: true };
|
|
7695
7794
|
}
|
|
7696
7795
|
async function confirmProjectRoot(cwd, opts) {
|
|
7697
|
-
const resolved =
|
|
7796
|
+
const resolved = path33.resolve(cwd);
|
|
7698
7797
|
if (opts.forceRoot) {
|
|
7699
7798
|
return resolved;
|
|
7700
7799
|
}
|
|
@@ -7808,9 +7907,9 @@ function npxPinned(runtimeVersion, subcommand) {
|
|
|
7808
7907
|
return `npx -y ${pinnedCliSpec(runtimeVersion)} ${subcommand}`;
|
|
7809
7908
|
}
|
|
7810
7909
|
function isBinUnderPrefix(binPath, prefix) {
|
|
7811
|
-
const bin =
|
|
7812
|
-
const root =
|
|
7813
|
-
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}`);
|
|
7814
7913
|
}
|
|
7815
7914
|
function pathCliStatus(env, runtimeVersion) {
|
|
7816
7915
|
if (!env.binOnPath || !env.binPath) return "missing";
|
|
@@ -7908,17 +8007,17 @@ async function syncPathCliToRuntime(options = {}) {
|
|
|
7908
8007
|
}
|
|
7909
8008
|
|
|
7910
8009
|
// src/commands/install.ts
|
|
7911
|
-
import
|
|
8010
|
+
import path40 from "path";
|
|
7912
8011
|
import { defineCommand as defineCommand11 } from "citty";
|
|
7913
8012
|
import { bold, cyan as cyan3, green as green2, options as koloristOptions2 } from "kolorist";
|
|
7914
8013
|
|
|
7915
8014
|
// src/generator/personalization.ts
|
|
7916
8015
|
import { readFile as readFile21, writeFile as writeFile12 } from "fs/promises";
|
|
7917
|
-
import
|
|
8016
|
+
import path38 from "path";
|
|
7918
8017
|
|
|
7919
8018
|
// src/generator/claude-command-adapters.ts
|
|
7920
|
-
import { readFile as
|
|
7921
|
-
import
|
|
8019
|
+
import { readFile as readFile20, readdir as readdir9, writeFile as writeFile9 } from "fs/promises";
|
|
8020
|
+
import path35 from "path";
|
|
7922
8021
|
var CURSOR_COMMANDS_DIR_REL = ".cursor/commands";
|
|
7923
8022
|
var CLAUDE_COMMANDS_DIR_REL = ".claude/commands";
|
|
7924
8023
|
var RESERVED_ADAPTER_NAMES = /* @__PURE__ */ new Set(["agent-kit"]);
|
|
@@ -7947,7 +8046,7 @@ Adapter rules (Claude Code CLI):
|
|
|
7947
8046
|
`;
|
|
7948
8047
|
}
|
|
7949
8048
|
async function discoverInstalledCommands(rootDir) {
|
|
7950
|
-
const dir =
|
|
8049
|
+
const dir = path35.join(rootDir, CURSOR_COMMANDS_DIR_REL);
|
|
7951
8050
|
let entries;
|
|
7952
8051
|
try {
|
|
7953
8052
|
entries = (await readdir9(dir)).filter((f) => f.endsWith(".md"));
|
|
@@ -7959,7 +8058,7 @@ async function discoverInstalledCommands(rootDir) {
|
|
|
7959
8058
|
const name = file.slice(0, -3);
|
|
7960
8059
|
if (RESERVED_ADAPTER_NAMES.has(name)) continue;
|
|
7961
8060
|
try {
|
|
7962
|
-
const raw = await
|
|
8061
|
+
const raw = await readFile20(path35.join(dir, file), "utf8");
|
|
7963
8062
|
const parsed = parseCommandFrontmatter(name, raw);
|
|
7964
8063
|
if (parsed) commands.push(parsed);
|
|
7965
8064
|
} catch {
|
|
@@ -7974,18 +8073,18 @@ async function generateClaudeCommandAdapters(rootDir) {
|
|
|
7974
8073
|
const results = [];
|
|
7975
8074
|
let ledgerDirty = false;
|
|
7976
8075
|
for (const command of commands) {
|
|
7977
|
-
const relPath =
|
|
8076
|
+
const relPath = path35.posix.join(CLAUDE_COMMANDS_DIR_REL, `${command.name}.md`);
|
|
7978
8077
|
const rendered = renderClaudeCommandAdapter(command);
|
|
7979
|
-
const abs =
|
|
8078
|
+
const abs = path35.join(rootDir, relPath);
|
|
7980
8079
|
if (!await fileExists(abs)) {
|
|
7981
|
-
await ensureDir(
|
|
7982
|
-
await
|
|
8080
|
+
await ensureDir(path35.dirname(abs));
|
|
8081
|
+
await writeFile9(abs, rendered, "utf8");
|
|
7983
8082
|
ledger.hashes[relPath] = contentHash(rendered);
|
|
7984
8083
|
ledgerDirty = true;
|
|
7985
8084
|
results.push({ relativePath: relPath, status: "applied" });
|
|
7986
8085
|
continue;
|
|
7987
8086
|
}
|
|
7988
|
-
const localContent = await
|
|
8087
|
+
const localContent = await readFile20(abs, "utf8");
|
|
7989
8088
|
if (localContent === rendered) {
|
|
7990
8089
|
if (ledger.hashes[relPath] !== contentHash(rendered)) {
|
|
7991
8090
|
ledger.hashes[relPath] = contentHash(rendered);
|
|
@@ -7998,7 +8097,7 @@ async function generateClaudeCommandAdapters(rootDir) {
|
|
|
7998
8097
|
results.push({ relativePath: relPath, status: "preserved-customized" });
|
|
7999
8098
|
continue;
|
|
8000
8099
|
}
|
|
8001
|
-
await
|
|
8100
|
+
await writeFile9(abs, rendered, "utf8");
|
|
8002
8101
|
ledger.hashes[relPath] = contentHash(rendered);
|
|
8003
8102
|
ledgerDirty = true;
|
|
8004
8103
|
results.push({ relativePath: relPath, status: "refreshed" });
|
|
@@ -8008,8 +8107,8 @@ async function generateClaudeCommandAdapters(rootDir) {
|
|
|
8008
8107
|
}
|
|
8009
8108
|
|
|
8010
8109
|
// src/generator/claude-kit-load.ts
|
|
8011
|
-
import { writeFile as
|
|
8012
|
-
import
|
|
8110
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
8111
|
+
import path36 from "path";
|
|
8013
8112
|
var CLAUDE_MD_REL = "CLAUDE.md";
|
|
8014
8113
|
var AGENT_KIT_COMMAND_REL = ".claude/commands/agent-kit.md";
|
|
8015
8114
|
function renderClaudeMd() {
|
|
@@ -8065,12 +8164,12 @@ Non-goals: not audits / \`/plan-external-review\`, not a second tick dialect (\`
|
|
|
8065
8164
|
`;
|
|
8066
8165
|
}
|
|
8067
8166
|
async function writeUnlessExists(rootDir, relativePath, content) {
|
|
8068
|
-
const target =
|
|
8167
|
+
const target = path36.join(rootDir, relativePath);
|
|
8069
8168
|
if (await fileExists(target)) {
|
|
8070
8169
|
return { relativePath, status: "skipped-customized" };
|
|
8071
8170
|
}
|
|
8072
|
-
await ensureDir(
|
|
8073
|
-
await
|
|
8171
|
+
await ensureDir(path36.dirname(target));
|
|
8172
|
+
await writeFile10(target, content, "utf8");
|
|
8074
8173
|
return { relativePath, status: "applied" };
|
|
8075
8174
|
}
|
|
8076
8175
|
async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
@@ -8080,109 +8179,9 @@ async function generateClaudeKitLoadArtifacts(rootDir) {
|
|
|
8080
8179
|
]);
|
|
8081
8180
|
}
|
|
8082
8181
|
|
|
8083
|
-
// src/generator/claude-session-start-hook.ts
|
|
8084
|
-
import { readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
|
|
8085
|
-
import path35 from "path";
|
|
8086
|
-
var CLAUDE_SETTINGS_REL = ".claude/settings.json";
|
|
8087
|
-
var RESOLVE_AGENT_KIT_REL = ".cursor/hooks/agent/resolve-agent-kit.sh";
|
|
8088
|
-
var SESSION_START_HOOK_MARKER = "hook session-start --format claude";
|
|
8089
|
-
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.";
|
|
8090
|
-
function buildSessionStartHookCommand() {
|
|
8091
|
-
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)}`;
|
|
8092
|
-
}
|
|
8093
|
-
function shellSingleQuote(text) {
|
|
8094
|
-
return `'${text.replace(/'/g, "'\\''")}'`;
|
|
8095
|
-
}
|
|
8096
|
-
function buildSessionStartHookEntry() {
|
|
8097
|
-
return {
|
|
8098
|
-
type: "command",
|
|
8099
|
-
command: buildSessionStartHookCommand(),
|
|
8100
|
-
timeout: 15,
|
|
8101
|
-
statusMessage: "Loading Agent Kit session context"
|
|
8102
|
-
};
|
|
8103
|
-
}
|
|
8104
|
-
function isPlainObject2(value) {
|
|
8105
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8106
|
-
}
|
|
8107
|
-
function groupHasMarker(group) {
|
|
8108
|
-
if (!isPlainObject2(group) || !Array.isArray(group.hooks)) return false;
|
|
8109
|
-
return group.hooks.some(
|
|
8110
|
-
(h) => isPlainObject2(h) && typeof h.command === "string" && h.command.includes(SESSION_START_HOOK_MARKER)
|
|
8111
|
-
);
|
|
8112
|
-
}
|
|
8113
|
-
function mergeSessionStartHookIntoSettings(existingRaw) {
|
|
8114
|
-
const entry = buildSessionStartHookEntry();
|
|
8115
|
-
const newGroup = { hooks: [entry] };
|
|
8116
|
-
let root = {};
|
|
8117
|
-
if (existingRaw !== null && existingRaw.trim() !== "") {
|
|
8118
|
-
try {
|
|
8119
|
-
const parsed = JSON.parse(existingRaw);
|
|
8120
|
-
if (!isPlainObject2(parsed)) throw new Error("root is not an object");
|
|
8121
|
-
root = parsed;
|
|
8122
|
-
} catch {
|
|
8123
|
-
return {
|
|
8124
|
-
content: null,
|
|
8125
|
-
status: "unavailable",
|
|
8126
|
-
instructions: instructionsBlock(entry)
|
|
8127
|
-
};
|
|
8128
|
-
}
|
|
8129
|
-
}
|
|
8130
|
-
const hooks = isPlainObject2(root.hooks) ? { ...root.hooks } : {};
|
|
8131
|
-
const sessionStart = Array.isArray(hooks.SessionStart) ? [...hooks.SessionStart] : [];
|
|
8132
|
-
const existingIndex = sessionStart.findIndex((g) => groupHasMarker(g));
|
|
8133
|
-
let status;
|
|
8134
|
-
if (existingIndex === -1) {
|
|
8135
|
-
sessionStart.push(newGroup);
|
|
8136
|
-
status = "applied";
|
|
8137
|
-
} else {
|
|
8138
|
-
const current = sessionStart[existingIndex];
|
|
8139
|
-
if (JSON.stringify(current) === JSON.stringify(newGroup)) {
|
|
8140
|
-
status = "unchanged";
|
|
8141
|
-
} else {
|
|
8142
|
-
sessionStart[existingIndex] = newGroup;
|
|
8143
|
-
status = "refreshed";
|
|
8144
|
-
}
|
|
8145
|
-
}
|
|
8146
|
-
hooks.SessionStart = sessionStart;
|
|
8147
|
-
root.hooks = hooks;
|
|
8148
|
-
return { content: `${JSON.stringify(root, null, 2)}
|
|
8149
|
-
`, status };
|
|
8150
|
-
}
|
|
8151
|
-
function instructionsBlock(entry) {
|
|
8152
|
-
return [
|
|
8153
|
-
`Could not parse the existing ${CLAUDE_SETTINGS_REL} as JSON, so Agent Kit did not touch it.`,
|
|
8154
|
-
"Add this hook by hand under hooks.SessionStart (create the arrays if they do not exist):",
|
|
8155
|
-
"",
|
|
8156
|
-
JSON.stringify(entry, null, 2)
|
|
8157
|
-
].join("\n");
|
|
8158
|
-
}
|
|
8159
|
-
async function writeClaudeSessionStartHook(rootDir) {
|
|
8160
|
-
const abs = path35.join(rootDir, CLAUDE_SETTINGS_REL);
|
|
8161
|
-
let existing = null;
|
|
8162
|
-
try {
|
|
8163
|
-
existing = await readFile20(abs, "utf8");
|
|
8164
|
-
} catch {
|
|
8165
|
-
existing = null;
|
|
8166
|
-
}
|
|
8167
|
-
const merged = mergeSessionStartHookIntoSettings(existing);
|
|
8168
|
-
if (merged.status === "unavailable" || merged.content === null) {
|
|
8169
|
-
return {
|
|
8170
|
-
relativePath: CLAUDE_SETTINGS_REL,
|
|
8171
|
-
status: "unavailable",
|
|
8172
|
-
instructions: merged.instructions
|
|
8173
|
-
};
|
|
8174
|
-
}
|
|
8175
|
-
if (merged.status === "unchanged") {
|
|
8176
|
-
return { relativePath: CLAUDE_SETTINGS_REL, status: "unchanged" };
|
|
8177
|
-
}
|
|
8178
|
-
await ensureDir(path35.dirname(abs));
|
|
8179
|
-
await writeFile10(abs, merged.content, "utf8");
|
|
8180
|
-
return { relativePath: CLAUDE_SETTINGS_REL, status: merged.status };
|
|
8181
|
-
}
|
|
8182
|
-
|
|
8183
8182
|
// src/generator/vscode.ts
|
|
8184
8183
|
import { writeFile as writeFile11 } from "fs/promises";
|
|
8185
|
-
import
|
|
8184
|
+
import path37 from "path";
|
|
8186
8185
|
|
|
8187
8186
|
// src/generator/platform.ts
|
|
8188
8187
|
function gitProviderLabel(profile) {
|
|
@@ -8206,10 +8205,10 @@ function prTerminology(profile) {
|
|
|
8206
8205
|
// src/generator/vscode.ts
|
|
8207
8206
|
async function generateVSCodeArtifacts(profile) {
|
|
8208
8207
|
const results = [];
|
|
8209
|
-
const vscodeDir =
|
|
8210
|
-
const githubDir =
|
|
8208
|
+
const vscodeDir = path37.join(profile.rootDir, ".vscode");
|
|
8209
|
+
const githubDir = path37.join(profile.rootDir, ".github");
|
|
8211
8210
|
await Promise.all([ensureDir(vscodeDir), ensureDir(githubDir)]);
|
|
8212
|
-
const settingsPath =
|
|
8211
|
+
const settingsPath = path37.join(vscodeDir, "settings.json");
|
|
8213
8212
|
if (await fileExists(settingsPath)) {
|
|
8214
8213
|
results.push({ relativePath: ".vscode/settings.json", status: "skipped-customized" });
|
|
8215
8214
|
} else {
|
|
@@ -8233,7 +8232,7 @@ async function generateVSCodeArtifacts(profile) {
|
|
|
8233
8232
|
}
|
|
8234
8233
|
const provider = gitProviderLabel(profile);
|
|
8235
8234
|
const prTerm = prTerminology(profile);
|
|
8236
|
-
const copilotPath =
|
|
8235
|
+
const copilotPath = path37.join(githubDir, "copilot-instructions.md");
|
|
8237
8236
|
if (await fileExists(copilotPath)) {
|
|
8238
8237
|
results.push({ relativePath: ".github/copilot-instructions.md", status: "skipped-customized" });
|
|
8239
8238
|
} else {
|
|
@@ -8251,7 +8250,7 @@ async function generateVSCodeArtifacts(profile) {
|
|
|
8251
8250
|
results.push({ relativePath: ".github/copilot-instructions.md", status: "applied" });
|
|
8252
8251
|
}
|
|
8253
8252
|
if (profile.ide.plan === "vscode-pro") {
|
|
8254
|
-
const securityPath =
|
|
8253
|
+
const securityPath = path37.join(vscodeDir, "security-review.agent.md");
|
|
8255
8254
|
if (await fileExists(securityPath)) {
|
|
8256
8255
|
results.push({
|
|
8257
8256
|
relativePath: ".vscode/security-review.agent.md",
|
|
@@ -8463,7 +8462,7 @@ function renderProjectContext(profile, skillItems = []) {
|
|
|
8463
8462
|
`;
|
|
8464
8463
|
}
|
|
8465
8464
|
async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
8466
|
-
const target =
|
|
8465
|
+
const target = path38.join(rootDir, relativePath);
|
|
8467
8466
|
if (await fileExists(target)) {
|
|
8468
8467
|
return {
|
|
8469
8468
|
kind: "file",
|
|
@@ -8473,7 +8472,7 @@ async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
|
8473
8472
|
evidence
|
|
8474
8473
|
};
|
|
8475
8474
|
}
|
|
8476
|
-
await ensureDir(
|
|
8475
|
+
await ensureDir(path38.dirname(target));
|
|
8477
8476
|
await writeFile12(target, content, "utf8");
|
|
8478
8477
|
return {
|
|
8479
8478
|
kind: "file",
|
|
@@ -8490,7 +8489,7 @@ async function packTargets(registryRoot, packId) {
|
|
|
8490
8489
|
async function existingTargets(projectRoot, targets) {
|
|
8491
8490
|
const checks = await Promise.all(
|
|
8492
8491
|
targets.map(
|
|
8493
|
-
async (target) => await fileExists(
|
|
8492
|
+
async (target) => await fileExists(path38.join(projectRoot, target)) ? target : null
|
|
8494
8493
|
)
|
|
8495
8494
|
);
|
|
8496
8495
|
return checks.filter((target) => target !== null);
|
|
@@ -8512,14 +8511,14 @@ async function applyPersonalization(input) {
|
|
|
8512
8511
|
componentResults.push({ ...item, status: "unavailable" });
|
|
8513
8512
|
continue;
|
|
8514
8513
|
}
|
|
8515
|
-
const target =
|
|
8514
|
+
const target = path38.posix.join(
|
|
8516
8515
|
".cursor",
|
|
8517
8516
|
"skills",
|
|
8518
8517
|
skill.path.includes("/core/") ? "core" : "community",
|
|
8519
8518
|
skill.id,
|
|
8520
8519
|
"SKILL.md"
|
|
8521
8520
|
);
|
|
8522
|
-
if (await fileExists(
|
|
8521
|
+
if (await fileExists(path38.join(input.rootDir, target))) {
|
|
8523
8522
|
componentResults.push({ ...item, status: "skipped-customized", path: target });
|
|
8524
8523
|
protectedPaths.add(target);
|
|
8525
8524
|
continue;
|
|
@@ -8664,7 +8663,7 @@ async function applyPersonalization(input) {
|
|
|
8664
8663
|
protectedPaths: [...protectedPaths].sort(),
|
|
8665
8664
|
...claudeSessionStartInstructions ? { claudeSessionStartInstructions } : {}
|
|
8666
8665
|
};
|
|
8667
|
-
await writeJson(
|
|
8666
|
+
await writeJson(path38.join(input.rootDir, RESULT_PATH), result);
|
|
8668
8667
|
return {
|
|
8669
8668
|
result,
|
|
8670
8669
|
manifest: {
|
|
@@ -8682,7 +8681,7 @@ async function applyPersonalization(input) {
|
|
|
8682
8681
|
};
|
|
8683
8682
|
}
|
|
8684
8683
|
async function readRepositoryProfile(rootDir) {
|
|
8685
|
-
const target =
|
|
8684
|
+
const target = path38.join(rootDir, REPOSITORY_PROFILE_REL);
|
|
8686
8685
|
if (!await fileExists(target)) return null;
|
|
8687
8686
|
return JSON.parse(await readFile21(target, "utf8"));
|
|
8688
8687
|
}
|
|
@@ -8690,16 +8689,16 @@ async function readRepositoryProfile(rootDir) {
|
|
|
8690
8689
|
// src/lifecycle/onboard-migration.ts
|
|
8691
8690
|
import { createHash as createHash4 } from "crypto";
|
|
8692
8691
|
import { readFile as readFile22, unlink } from "fs/promises";
|
|
8693
|
-
import
|
|
8692
|
+
import path39 from "path";
|
|
8694
8693
|
var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
|
|
8695
8694
|
var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
|
|
8696
8695
|
var MANAGED_LEGACY_HASHES = /* @__PURE__ */ new Set([
|
|
8697
8696
|
"b274a68941813f19b185893cb7c5561dff027f53270890029992f208e24992fe"
|
|
8698
8697
|
]);
|
|
8699
8698
|
async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_LEGACY_HASHES) {
|
|
8700
|
-
const legacyPath =
|
|
8699
|
+
const legacyPath = path39.join(projectRoot, LEGACY_ONBOARD_PATH);
|
|
8701
8700
|
if (!await fileExists(legacyPath)) return "absent";
|
|
8702
|
-
const namespacedPath =
|
|
8701
|
+
const namespacedPath = path39.join(projectRoot, NAMESPACED_ONBOARD_PATH);
|
|
8703
8702
|
if (!await fileExists(namespacedPath)) return "preserved-customized";
|
|
8704
8703
|
const content = await readFile22(legacyPath);
|
|
8705
8704
|
const hash = createHash4("sha256").update(content).digest("hex");
|
|
@@ -8751,6 +8750,21 @@ async function syncFromManifest(registryRoot, projectRoot, manifest) {
|
|
|
8751
8750
|
return stats;
|
|
8752
8751
|
}
|
|
8753
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
|
+
|
|
8754
8768
|
// src/commands/install.ts
|
|
8755
8769
|
function parsePackList(raw) {
|
|
8756
8770
|
if (!raw?.trim()) return [];
|
|
@@ -8835,7 +8849,7 @@ async function printPostInstallSummary(result) {
|
|
|
8835
8849
|
printInstallEpilogue(sync.env, { runtimeVersion: KIT_VERSION });
|
|
8836
8850
|
}
|
|
8837
8851
|
async function performInstall(options) {
|
|
8838
|
-
const projectRoot =
|
|
8852
|
+
const projectRoot = path40.resolve(options.cwd);
|
|
8839
8853
|
const packs = parsePackList(options.pack);
|
|
8840
8854
|
const existing = await loadAgentKitManifest(projectRoot);
|
|
8841
8855
|
const registry = await resolveRegistryFromCli({
|
|
@@ -8912,17 +8926,7 @@ var installCommand = defineCommand11({
|
|
|
8912
8926
|
type: "string",
|
|
8913
8927
|
description: `Comma-separated L1 pack ids (e.g. cybersec,devops). Known: ${DOMAIN_PACK_IDS.join(", ")}`
|
|
8914
8928
|
},
|
|
8915
|
-
|
|
8916
|
-
type: "boolean",
|
|
8917
|
-
alias: "y",
|
|
8918
|
-
description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
|
|
8919
|
-
default: false
|
|
8920
|
-
},
|
|
8921
|
-
"force-root": {
|
|
8922
|
-
type: "boolean",
|
|
8923
|
-
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
8924
|
-
default: false
|
|
8925
|
-
},
|
|
8929
|
+
...NON_INTERACTIVE_ROOT_ARGS,
|
|
8926
8930
|
claude: {
|
|
8927
8931
|
type: "boolean",
|
|
8928
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)",
|
|
@@ -9017,17 +9021,7 @@ var initCommand = defineCommand12({
|
|
|
9017
9021
|
description: "Project root directory",
|
|
9018
9022
|
default: process.cwd()
|
|
9019
9023
|
},
|
|
9020
|
-
|
|
9021
|
-
type: "boolean",
|
|
9022
|
-
alias: "y",
|
|
9023
|
-
description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
|
|
9024
|
-
default: false
|
|
9025
|
-
},
|
|
9026
|
-
"force-root": {
|
|
9027
|
-
type: "boolean",
|
|
9028
|
-
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
9029
|
-
default: false
|
|
9030
|
-
}
|
|
9024
|
+
...NON_INTERACTIVE_ROOT_ARGS
|
|
9031
9025
|
},
|
|
9032
9026
|
async run({ args }) {
|
|
9033
9027
|
const nonInteractive = args.yes || isNonInteractive();
|
|
@@ -9561,8 +9555,8 @@ function renderMcLive(view, opts = {}) {
|
|
|
9561
9555
|
|
|
9562
9556
|
// src/mission-control/snapshot.ts
|
|
9563
9557
|
import { execFile as execFile6 } from "child_process";
|
|
9564
|
-
import { access as
|
|
9565
|
-
import
|
|
9558
|
+
import { access as access7 } from "fs/promises";
|
|
9559
|
+
import path41 from "path";
|
|
9566
9560
|
|
|
9567
9561
|
// ../../dashboard/lib/live-refresh.mjs
|
|
9568
9562
|
import { join, resolve as resolve2 } from "path";
|
|
@@ -9594,8 +9588,8 @@ function dataScriptTimeoutMs(env = process.env) {
|
|
|
9594
9588
|
async function firstExisting2(candidates) {
|
|
9595
9589
|
for (const candidate2 of candidates) {
|
|
9596
9590
|
try {
|
|
9597
|
-
await
|
|
9598
|
-
return
|
|
9591
|
+
await access7(candidate2);
|
|
9592
|
+
return path41.resolve(candidate2);
|
|
9599
9593
|
} catch {
|
|
9600
9594
|
}
|
|
9601
9595
|
}
|
|
@@ -9604,9 +9598,9 @@ async function firstExisting2(candidates) {
|
|
|
9604
9598
|
async function findDashboardDataScript(cwd, env = process.env, options = {}) {
|
|
9605
9599
|
const startPath = await findDashboardStart(cwd, env, options);
|
|
9606
9600
|
if (startPath) {
|
|
9607
|
-
const sibling =
|
|
9601
|
+
const sibling = path41.join(path41.dirname(startPath), "dashboard-data.mjs");
|
|
9608
9602
|
try {
|
|
9609
|
-
await
|
|
9603
|
+
await access7(sibling);
|
|
9610
9604
|
return sibling;
|
|
9611
9605
|
} catch {
|
|
9612
9606
|
}
|
|
@@ -9619,7 +9613,7 @@ async function loadDashboardSnapshot(opts) {
|
|
|
9619
9613
|
const env = opts.env ?? process.env;
|
|
9620
9614
|
const timeout = dataScriptTimeoutMs(env);
|
|
9621
9615
|
const run = opts.execFileFn ?? execFile6;
|
|
9622
|
-
const snapshotRoot =
|
|
9616
|
+
const snapshotRoot = path41.resolve(opts.snapshotRoot);
|
|
9623
9617
|
return new Promise((resolve3) => {
|
|
9624
9618
|
run(
|
|
9625
9619
|
process.execPath,
|
|
@@ -9942,13 +9936,13 @@ var missionControlCommand = defineCommand13({
|
|
|
9942
9936
|
});
|
|
9943
9937
|
|
|
9944
9938
|
// src/commands/monitors.ts
|
|
9945
|
-
import
|
|
9939
|
+
import path43 from "path";
|
|
9946
9940
|
import { defineCommand as defineCommand14 } from "citty";
|
|
9947
9941
|
|
|
9948
9942
|
// src/invariants/monitors-untriaged.ts
|
|
9949
9943
|
import { execFile as execFile7 } from "child_process";
|
|
9950
9944
|
import { readFile as readFile23, readdir as readdir10, stat as stat5 } from "fs/promises";
|
|
9951
|
-
import
|
|
9945
|
+
import path42 from "path";
|
|
9952
9946
|
import { promisify as promisify6 } from "util";
|
|
9953
9947
|
|
|
9954
9948
|
// src/invariants/triage-heading.ts
|
|
@@ -9989,7 +9983,7 @@ async function gitFreshMonitorNames(rootDir) {
|
|
|
9989
9983
|
for (const line of stdout.split("\n")) {
|
|
9990
9984
|
if (!line.trim()) continue;
|
|
9991
9985
|
const file = line.slice(3).trim().replace(/^.* -> /, "");
|
|
9992
|
-
const base =
|
|
9986
|
+
const base = path42.basename(file);
|
|
9993
9987
|
if (base.startsWith("plan-monitor-") && base.endsWith(".md")) {
|
|
9994
9988
|
names.add(base);
|
|
9995
9989
|
}
|
|
@@ -10012,13 +10006,13 @@ function monitorSlugFromName(fileName) {
|
|
|
10012
10006
|
return fileName.replace(/^plan-monitor-/, "").replace(/\.md$/, "").toLowerCase();
|
|
10013
10007
|
}
|
|
10014
10008
|
async function selectUntriagedMonitors(rootDir) {
|
|
10015
|
-
const root =
|
|
10016
|
-
const memoryDir =
|
|
10009
|
+
const root = path42.resolve(rootDir);
|
|
10010
|
+
const memoryDir = path42.join(root, ".cursor", "memory");
|
|
10017
10011
|
const allNames = await listMonitorFiles(memoryDir);
|
|
10018
10012
|
const selectionOrder = ["git-fresh", "handoff-aligned", "untriaged-scan"];
|
|
10019
10013
|
const byName = /* @__PURE__ */ new Map();
|
|
10020
10014
|
for (const name of allNames) {
|
|
10021
|
-
const abs =
|
|
10015
|
+
const abs = path42.join(memoryDir, name);
|
|
10022
10016
|
try {
|
|
10023
10017
|
const [content, st] = await Promise.all([readFile23(abs, "utf8"), stat5(abs)]);
|
|
10024
10018
|
byName.set(name, { content, mtimeMs: st.mtimeMs });
|
|
@@ -10033,7 +10027,7 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
10033
10027
|
const gitFreshSet = [...gitFresh].filter(untriaged).sort();
|
|
10034
10028
|
let handoff = "";
|
|
10035
10029
|
try {
|
|
10036
|
-
handoff = await readFile23(
|
|
10030
|
+
handoff = await readFile23(path42.join(root, ".cursor", "HANDOFF.md"), "utf8");
|
|
10037
10031
|
} catch {
|
|
10038
10032
|
handoff = "";
|
|
10039
10033
|
}
|
|
@@ -10053,8 +10047,8 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
10053
10047
|
const row = byName.get(name);
|
|
10054
10048
|
if (!row) continue;
|
|
10055
10049
|
entries.push({
|
|
10056
|
-
path:
|
|
10057
|
-
relativePath:
|
|
10050
|
+
path: path42.join(memoryDir, name),
|
|
10051
|
+
relativePath: path42.relative(root, path42.join(memoryDir, name)).split(path42.sep).join("/"),
|
|
10058
10052
|
mtimeMs: row.mtimeMs,
|
|
10059
10053
|
hasTriageHeading: false,
|
|
10060
10054
|
hasOpenGaps: hasOpenGaps(row.content),
|
|
@@ -10100,7 +10094,7 @@ var monitorsCommand = defineCommand14({
|
|
|
10100
10094
|
process.exitCode = 2;
|
|
10101
10095
|
return;
|
|
10102
10096
|
}
|
|
10103
|
-
const result = await selectUntriagedMonitors(
|
|
10097
|
+
const result = await selectUntriagedMonitors(path43.resolve(args.cwd));
|
|
10104
10098
|
if (args.json) {
|
|
10105
10099
|
console.log(JSON.stringify(result, null, 2));
|
|
10106
10100
|
return;
|
|
@@ -10116,7 +10110,7 @@ var monitorsCommand = defineCommand14({
|
|
|
10116
10110
|
});
|
|
10117
10111
|
|
|
10118
10112
|
// src/commands/plan-index.ts
|
|
10119
|
-
import
|
|
10113
|
+
import path44 from "path";
|
|
10120
10114
|
import { defineCommand as defineCommand15 } from "citty";
|
|
10121
10115
|
var planIndexCommand = defineCommand15({
|
|
10122
10116
|
meta: {
|
|
@@ -10135,7 +10129,7 @@ var planIndexCommand = defineCommand15({
|
|
|
10135
10129
|
}
|
|
10136
10130
|
},
|
|
10137
10131
|
async run({ args }) {
|
|
10138
|
-
const root =
|
|
10132
|
+
const root = path44.resolve(args.cwd);
|
|
10139
10133
|
const index = await writePlanIndex(root);
|
|
10140
10134
|
if (args.json) {
|
|
10141
10135
|
console.log(JSON.stringify(index, null, 2));
|
|
@@ -10151,14 +10145,14 @@ var planIndexCommand = defineCommand15({
|
|
|
10151
10145
|
});
|
|
10152
10146
|
|
|
10153
10147
|
// src/commands/run-plan.ts
|
|
10154
|
-
import
|
|
10148
|
+
import path51 from "path";
|
|
10155
10149
|
import { defineCommand as defineCommand16 } from "citty";
|
|
10156
10150
|
|
|
10157
10151
|
// src/plan-loop/backends.ts
|
|
10158
10152
|
import { execFileSync as execFileSync2, spawn as spawn7 } from "child_process";
|
|
10159
10153
|
import { createWriteStream } from "fs";
|
|
10160
|
-
import { access as
|
|
10161
|
-
import
|
|
10154
|
+
import { access as access8 } from "fs/promises";
|
|
10155
|
+
import path45 from "path";
|
|
10162
10156
|
import { StringDecoder as StringDecoder3 } from "string_decoder";
|
|
10163
10157
|
|
|
10164
10158
|
// src/plan-loop/hitl-relay.ts
|
|
@@ -11186,26 +11180,32 @@ function createRelay(hitl, child, redactedError) {
|
|
|
11186
11180
|
}
|
|
11187
11181
|
};
|
|
11188
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
|
+
}
|
|
11189
11198
|
var cursorAgentBackend = {
|
|
11190
11199
|
id: "cursor-agent",
|
|
11191
11200
|
async resolve() {
|
|
11192
11201
|
return whichBinary("cursor-agent");
|
|
11193
11202
|
},
|
|
11194
11203
|
async run(opts) {
|
|
11195
|
-
const args =
|
|
11196
|
-
|
|
11197
|
-
|
|
11198
|
-
|
|
11199
|
-
|
|
11200
|
-
"--output-format",
|
|
11201
|
-
"stream-json",
|
|
11202
|
-
"--workspace",
|
|
11203
|
-
opts.workspace
|
|
11204
|
-
];
|
|
11205
|
-
if (opts.model) {
|
|
11206
|
-
args.push("--model", opts.model);
|
|
11207
|
-
}
|
|
11208
|
-
args.push(opts.prompt);
|
|
11204
|
+
const args = cursorAgentHeadlessArgs({
|
|
11205
|
+
workspace: opts.workspace,
|
|
11206
|
+
prompt: opts.prompt,
|
|
11207
|
+
model: opts.model
|
|
11208
|
+
});
|
|
11209
11209
|
const env = opts.env ? mergeChildEnv(opts.env) : void 0;
|
|
11210
11210
|
return spawnLogged("cursor-agent", args, opts.logPath, {
|
|
11211
11211
|
spawnFn: opts.spawnFn,
|
|
@@ -11292,9 +11292,9 @@ function claudeHeadlessArgs(opts = {}) {
|
|
|
11292
11292
|
async function missingClaudeAdapter(workspace, prompt) {
|
|
11293
11293
|
const m = /^\/([A-Za-z0-9][A-Za-z0-9_-]*)/.exec(prompt.trimStart());
|
|
11294
11294
|
if (!m?.[1]) return null;
|
|
11295
|
-
const rel =
|
|
11295
|
+
const rel = path45.join(".claude", "commands", `${m[1]}.md`);
|
|
11296
11296
|
try {
|
|
11297
|
-
await
|
|
11297
|
+
await access8(path45.join(workspace, rel));
|
|
11298
11298
|
return null;
|
|
11299
11299
|
} catch {
|
|
11300
11300
|
return rel;
|
|
@@ -11417,15 +11417,15 @@ async function detectAgentBackend(requested, whichFn = whichBinary) {
|
|
|
11417
11417
|
|
|
11418
11418
|
// src/plan-loop/run-loop.ts
|
|
11419
11419
|
import { mkdir as mkdir6, readFile as readFile27, rm as rm2, unlink as unlink2 } from "fs/promises";
|
|
11420
|
-
import
|
|
11420
|
+
import path50 from "path";
|
|
11421
11421
|
|
|
11422
11422
|
// src/mission-control/live-run.ts
|
|
11423
11423
|
import { readFile as readFile25 } from "fs/promises";
|
|
11424
|
-
import
|
|
11424
|
+
import path47 from "path";
|
|
11425
11425
|
|
|
11426
11426
|
// src/plan-loop/plan-state.ts
|
|
11427
11427
|
import { readFile as readFile24, readdir as readdir11 } from "fs/promises";
|
|
11428
|
-
import
|
|
11428
|
+
import path46 from "path";
|
|
11429
11429
|
function countPendingTodos(raw) {
|
|
11430
11430
|
const lines = raw.split(/\r?\n/);
|
|
11431
11431
|
let inFront = 0;
|
|
@@ -11453,7 +11453,7 @@ function countPendingTodos(raw) {
|
|
|
11453
11453
|
async function findActivePlanFile(plansDir) {
|
|
11454
11454
|
if (!await fileExists(plansDir)) return null;
|
|
11455
11455
|
const files = (await readdir11(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
11456
|
-
return files[0] ?
|
|
11456
|
+
return files[0] ? path46.join(plansDir, files[0]) : null;
|
|
11457
11457
|
}
|
|
11458
11458
|
async function readPlan(planPath) {
|
|
11459
11459
|
return readFile24(planPath, "utf8");
|
|
@@ -11641,7 +11641,7 @@ function firstWord(raw) {
|
|
|
11641
11641
|
}
|
|
11642
11642
|
async function readLiveMission(root, io = defaultMissionIo()) {
|
|
11643
11643
|
const mission = emptyLiveMission();
|
|
11644
|
-
const handoff = await io.readFile(
|
|
11644
|
+
const handoff = await io.readFile(path47.join(root, ".cursor", "HANDOFF.md")) ?? "";
|
|
11645
11645
|
const named = extractHandoffNamedPlans(handoff);
|
|
11646
11646
|
mission.mode = firstWord(MODE_RE.exec(handoff)?.[1]);
|
|
11647
11647
|
let cursorIndex = null;
|
|
@@ -11677,13 +11677,13 @@ async function readLiveMission(root, io = defaultMissionIo()) {
|
|
|
11677
11677
|
state: finished || cursor !== null && i < cursor ? "done" : i === cursor ? "current" : "pending"
|
|
11678
11678
|
}));
|
|
11679
11679
|
}
|
|
11680
|
-
const plansDir =
|
|
11680
|
+
const plansDir = path47.join(root, ".cursor", "plans");
|
|
11681
11681
|
const planBase = (mission.queue && !finished ? mission.queue.current : null) ?? named.active;
|
|
11682
|
-
const planPath = planBase ?
|
|
11682
|
+
const planPath = planBase ? path47.join(plansDir, planBase) : await io.findActivePlan(plansDir);
|
|
11683
11683
|
if (planPath) {
|
|
11684
11684
|
const raw = await io.readFile(planPath);
|
|
11685
11685
|
if (raw !== null) {
|
|
11686
|
-
mission.planFile =
|
|
11686
|
+
mission.planFile = path47.basename(planPath);
|
|
11687
11687
|
const { todos } = parsePlanFrontmatter2(raw);
|
|
11688
11688
|
const open = todos.filter((t) => t.status !== "cancelled");
|
|
11689
11689
|
mission.todos = {
|
|
@@ -11734,7 +11734,7 @@ async function withLiveTui(opts, run) {
|
|
|
11734
11734
|
columns,
|
|
11735
11735
|
now: now2,
|
|
11736
11736
|
backend: opts.backend,
|
|
11737
|
-
logPath:
|
|
11737
|
+
logPath: path47.relative(opts.root, opts.logPath) || opts.logPath
|
|
11738
11738
|
});
|
|
11739
11739
|
opts.onFeed?.(feed);
|
|
11740
11740
|
const missionLoader = opts.missionLoader ?? createLiveMissionLoader(opts.root, { now: now2 });
|
|
@@ -11871,9 +11871,9 @@ async function withLiveTui(opts, run) {
|
|
|
11871
11871
|
|
|
11872
11872
|
// src/plan-loop/external-review.ts
|
|
11873
11873
|
import { spawn as spawn8 } from "child_process";
|
|
11874
|
-
import
|
|
11875
|
-
var CANONICAL_REL =
|
|
11876
|
-
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");
|
|
11877
11877
|
function isPlanExhaustedReason(reason) {
|
|
11878
11878
|
const r = reason.trim().toLowerCase();
|
|
11879
11879
|
if (!r) return false;
|
|
@@ -11895,8 +11895,8 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
11895
11895
|
const existsFn = options.existsFn ?? fileExists;
|
|
11896
11896
|
const log = options.log ?? ((line) => console.log(line));
|
|
11897
11897
|
const force = options.force === true;
|
|
11898
|
-
const canonicalPath =
|
|
11899
|
-
const fallbackPath =
|
|
11898
|
+
const canonicalPath = path48.join(root, CANONICAL_REL);
|
|
11899
|
+
const fallbackPath = path48.join(root, FALLBACK_REL);
|
|
11900
11900
|
let scriptPath = null;
|
|
11901
11901
|
let scriptRel = CANONICAL_REL;
|
|
11902
11902
|
if (await existsFn(canonicalPath)) {
|
|
@@ -11949,7 +11949,7 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
11949
11949
|
}
|
|
11950
11950
|
|
|
11951
11951
|
// src/plan-loop/persona-banners.ts
|
|
11952
|
-
import
|
|
11952
|
+
import path49 from "path";
|
|
11953
11953
|
import {
|
|
11954
11954
|
blue,
|
|
11955
11955
|
cyan as cyan4,
|
|
@@ -11985,7 +11985,7 @@ function resolveColor(name, fallback) {
|
|
|
11985
11985
|
async function resolveCliPersonaId(root) {
|
|
11986
11986
|
try {
|
|
11987
11987
|
const cfg = await readJson(
|
|
11988
|
-
|
|
11988
|
+
path49.join(root, ".cursor", "context", "config.json")
|
|
11989
11989
|
);
|
|
11990
11990
|
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
11991
11991
|
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
@@ -11996,7 +11996,7 @@ async function resolveCliPersonaId(root) {
|
|
|
11996
11996
|
}
|
|
11997
11997
|
async function loadPersonaPack(root, personaId) {
|
|
11998
11998
|
try {
|
|
11999
|
-
const personaPath =
|
|
11999
|
+
const personaPath = path49.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
12000
12000
|
const pack = await readJson(personaPath);
|
|
12001
12001
|
if (!pack || typeof pack.id !== "string") return null;
|
|
12002
12002
|
return pack;
|
|
@@ -12175,9 +12175,9 @@ function sleep(ms) {
|
|
|
12175
12175
|
return new Promise((r) => setTimeout(r, ms));
|
|
12176
12176
|
}
|
|
12177
12177
|
async function runPlanLoop(opts) {
|
|
12178
|
-
const plansDir =
|
|
12179
|
-
const stopFile =
|
|
12180
|
-
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");
|
|
12181
12181
|
const planPath = await findActivePlanFile(plansDir);
|
|
12182
12182
|
if (!planPath) {
|
|
12183
12183
|
logger.error("No active plan in .cursor/plans/");
|
|
@@ -12198,7 +12198,7 @@ async function runPlanLoop(opts) {
|
|
|
12198
12198
|
try {
|
|
12199
12199
|
const persona = await loadCliRunPlanPersona(opts.root);
|
|
12200
12200
|
const banners = createPersonaBannerPrinter(persona);
|
|
12201
|
-
console.log(`Active plan: ${
|
|
12201
|
+
console.log(`Active plan: ${path50.basename(planPath)}`);
|
|
12202
12202
|
console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
|
|
12203
12203
|
console.log(`Backend: ${opts.backend.id}`);
|
|
12204
12204
|
if (persona) {
|
|
@@ -12242,8 +12242,8 @@ async function runPlanLoop(opts) {
|
|
|
12242
12242
|
planExhausted = true;
|
|
12243
12243
|
break;
|
|
12244
12244
|
}
|
|
12245
|
-
const logPath =
|
|
12246
|
-
const relLog =
|
|
12245
|
+
const logPath = path50.join(logDir, `tick-${stamp()}.log`);
|
|
12246
|
+
const relLog = path50.relative(opts.root, logPath);
|
|
12247
12247
|
console.log("");
|
|
12248
12248
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
12249
12249
|
if (banners) banners.tickStart(tickLine);
|
|
@@ -12351,7 +12351,7 @@ async function runPlanLoop(opts) {
|
|
|
12351
12351
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
12352
12352
|
if (banners) banners.phaseComplete(finishDetail);
|
|
12353
12353
|
console.log(
|
|
12354
|
-
`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)}/`
|
|
12355
12355
|
);
|
|
12356
12356
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
12357
12357
|
await armExternalPlanReview(opts.root);
|
|
@@ -12443,7 +12443,7 @@ var runPlanCommand = defineCommand16({
|
|
|
12443
12443
|
return;
|
|
12444
12444
|
}
|
|
12445
12445
|
const code = await runPlanLoop({
|
|
12446
|
-
root:
|
|
12446
|
+
root: path51.resolve(args.cwd),
|
|
12447
12447
|
maxTicks,
|
|
12448
12448
|
sleepSeconds,
|
|
12449
12449
|
model: args.model ? String(args.model) : void 0,
|
|
@@ -12458,12 +12458,12 @@ var runPlanCommand = defineCommand16({
|
|
|
12458
12458
|
|
|
12459
12459
|
// src/commands/run.ts
|
|
12460
12460
|
import { homedir as homedir4 } from "os";
|
|
12461
|
-
import
|
|
12461
|
+
import path53 from "path";
|
|
12462
12462
|
import { defineCommand as defineCommand17 } from "citty";
|
|
12463
12463
|
|
|
12464
12464
|
// src/plan-loop/dispatch.ts
|
|
12465
12465
|
import { mkdir as mkdir7, readFile as readFile28 } from "fs/promises";
|
|
12466
|
-
import
|
|
12466
|
+
import path52 from "path";
|
|
12467
12467
|
var RUN_CATALOG = [
|
|
12468
12468
|
"run-plan",
|
|
12469
12469
|
"run-plan-all",
|
|
@@ -12477,7 +12477,7 @@ var RUN_CATALOG = [
|
|
|
12477
12477
|
"kit-staging"
|
|
12478
12478
|
];
|
|
12479
12479
|
var FIRST_CLASS_SLASH_COMMANDS = ["run-plan", "run-plan-all"];
|
|
12480
|
-
var RUN_PROMOTE_BLOCKED =
|
|
12480
|
+
var RUN_PROMOTE_BLOCKED = RESERVED_GATE_IDS;
|
|
12481
12481
|
function normalizeSlashName(raw) {
|
|
12482
12482
|
return raw.replace(/^\//, "").replace(/\.md$/i, "").trim();
|
|
12483
12483
|
}
|
|
@@ -12507,7 +12507,7 @@ function rewriteRootArgvToRun(argv) {
|
|
|
12507
12507
|
return next;
|
|
12508
12508
|
}
|
|
12509
12509
|
function commandMarkdownPath(root, name) {
|
|
12510
|
-
return
|
|
12510
|
+
return path52.join(root, ".cursor", "commands", `${normalizeSlashName(name)}.md`);
|
|
12511
12511
|
}
|
|
12512
12512
|
function buildDispatchPrompt(commandBody) {
|
|
12513
12513
|
return [
|
|
@@ -12541,19 +12541,7 @@ function stamp2() {
|
|
|
12541
12541
|
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}${p(d.getMilliseconds(), 3)}`;
|
|
12542
12542
|
}
|
|
12543
12543
|
function cursorAgentDispatchArgs(opts) {
|
|
12544
|
-
|
|
12545
|
-
"-p",
|
|
12546
|
-
"--force",
|
|
12547
|
-
"--sandbox",
|
|
12548
|
-
"disabled",
|
|
12549
|
-
"--output-format",
|
|
12550
|
-
"stream-json",
|
|
12551
|
-
"--workspace",
|
|
12552
|
-
opts.workspace
|
|
12553
|
-
];
|
|
12554
|
-
if (opts.model) args.push("--model", opts.model);
|
|
12555
|
-
args.push(opts.prompt);
|
|
12556
|
-
return args;
|
|
12544
|
+
return cursorAgentHeadlessArgs(opts);
|
|
12557
12545
|
}
|
|
12558
12546
|
function claudeDispatchArgs(opts = {}) {
|
|
12559
12547
|
return claudeHeadlessArgs(opts);
|
|
@@ -12607,20 +12595,20 @@ async function runHeadlessDispatch(opts) {
|
|
|
12607
12595
|
});
|
|
12608
12596
|
}
|
|
12609
12597
|
async function ensureDispatchLogPath(root) {
|
|
12610
|
-
const logDir =
|
|
12598
|
+
const logDir = path52.join(root, ".cursor", "loop-logs");
|
|
12611
12599
|
await mkdir7(logDir, { recursive: true });
|
|
12612
|
-
return
|
|
12600
|
+
return path52.join(logDir, `run-${stamp2()}.log`);
|
|
12613
12601
|
}
|
|
12614
12602
|
|
|
12615
12603
|
// src/commands/run.ts
|
|
12616
12604
|
function displayPath(target, home = homedir4()) {
|
|
12617
|
-
const resolvedHome =
|
|
12605
|
+
const resolvedHome = path53.resolve(home);
|
|
12618
12606
|
if (target === resolvedHome) return "~";
|
|
12619
|
-
const prefix = resolvedHome.endsWith(
|
|
12620
|
-
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;
|
|
12621
12609
|
}
|
|
12622
12610
|
async function executeRun(input) {
|
|
12623
|
-
const root =
|
|
12611
|
+
const root = path53.resolve(input.cwd);
|
|
12624
12612
|
const slash = normalizeSlashName(input.slash);
|
|
12625
12613
|
if (!slash) {
|
|
12626
12614
|
return { exitCode: 1, error: "Provide a slash: agent-kit run <slash>" };
|
|
@@ -12671,7 +12659,7 @@ async function executeRun(input) {
|
|
|
12671
12659
|
const stdout = [
|
|
12672
12660
|
"--dry-run: no agent will be started.",
|
|
12673
12661
|
`Slash: ${slash}`,
|
|
12674
|
-
`Command file: ${
|
|
12662
|
+
`Command file: ${path53.relative(root, file.path)}`,
|
|
12675
12663
|
`Backend: ${detected.id} (${displayPath(detected.bin)})`,
|
|
12676
12664
|
"Prompt:",
|
|
12677
12665
|
prompt
|
|
@@ -12680,8 +12668,8 @@ async function executeRun(input) {
|
|
|
12680
12668
|
}
|
|
12681
12669
|
const logPath = await ensureDispatchLogPath(root);
|
|
12682
12670
|
console.log(`Dispatch: ${slash} via ${detected.id}`);
|
|
12683
|
-
console.log(`Command file: ${
|
|
12684
|
-
console.log(`Log: ${
|
|
12671
|
+
console.log(`Command file: ${path53.relative(root, file.path)}`);
|
|
12672
|
+
console.log(`Log: ${path53.relative(root, logPath)}`);
|
|
12685
12673
|
try {
|
|
12686
12674
|
const dispatch = {
|
|
12687
12675
|
backendId: detected.id,
|
|
@@ -12838,7 +12826,7 @@ var scanCommand = defineCommand18({
|
|
|
12838
12826
|
import { spawn as spawn9 } from "child_process";
|
|
12839
12827
|
import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile29, writeFile as writeFile13 } from "fs/promises";
|
|
12840
12828
|
import { homedir as homedir5 } from "os";
|
|
12841
|
-
import
|
|
12829
|
+
import path54 from "path";
|
|
12842
12830
|
import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
|
|
12843
12831
|
import { defineCommand as defineCommand19 } from "citty";
|
|
12844
12832
|
import { cyan as cyan5, green as green4, yellow as yellow3 } from "kolorist";
|
|
@@ -12847,9 +12835,9 @@ var SETUP_GLOBAL_MARKER = "# agent-kit setup-global";
|
|
|
12847
12835
|
function planSetupGlobalSteps(env, options = {}) {
|
|
12848
12836
|
const homeDir = options.homeDir ?? homedir5();
|
|
12849
12837
|
const packageSpec = options.packageSpec ?? pinnedCliSpec();
|
|
12850
|
-
const npmGlobalDir =
|
|
12851
|
-
const npmGlobalBin =
|
|
12852
|
-
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");
|
|
12853
12841
|
const npmrcPrefixValue = `~/${NPM_GLOBAL_DIR_NAME}`;
|
|
12854
12842
|
const pathExportLine = `export PATH="${npmGlobalBin}:$PATH"`;
|
|
12855
12843
|
const shellProfile = env.shellProfile;
|
|
@@ -13148,7 +13136,7 @@ var setupGlobalCommand = defineCommand19({
|
|
|
13148
13136
|
});
|
|
13149
13137
|
|
|
13150
13138
|
// src/commands/status.ts
|
|
13151
|
-
import
|
|
13139
|
+
import path55 from "path";
|
|
13152
13140
|
import { defineCommand as defineCommand20 } from "citty";
|
|
13153
13141
|
function profileStatus(profile) {
|
|
13154
13142
|
if (!profile) return { origin: "none", evidence: [], profile: null };
|
|
@@ -13179,11 +13167,11 @@ var statusCommand = defineCommand20({
|
|
|
13179
13167
|
}
|
|
13180
13168
|
},
|
|
13181
13169
|
async run({ args }) {
|
|
13182
|
-
const rootDir =
|
|
13170
|
+
const rootDir = path55.resolve(args.cwd);
|
|
13183
13171
|
const [manifest, rawProfile, scan, env] = await Promise.all([
|
|
13184
13172
|
loadAgentKitManifest(rootDir),
|
|
13185
13173
|
readJson(
|
|
13186
|
-
|
|
13174
|
+
path55.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
13187
13175
|
),
|
|
13188
13176
|
runScanner(rootDir),
|
|
13189
13177
|
assessEnvironment()
|
|
@@ -13291,17 +13279,7 @@ var updateCommand = defineCommand21({
|
|
|
13291
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)",
|
|
13292
13280
|
default: false
|
|
13293
13281
|
},
|
|
13294
|
-
|
|
13295
|
-
type: "boolean",
|
|
13296
|
-
alias: "y",
|
|
13297
|
-
description: "Skip interactive prompts; use defaults (IDE-agnostic non-interactive mode)",
|
|
13298
|
-
default: false
|
|
13299
|
-
},
|
|
13300
|
-
"force-root": {
|
|
13301
|
-
type: "boolean",
|
|
13302
|
-
description: "Bypass the ambiguous-root guard (use with caution)",
|
|
13303
|
-
default: false
|
|
13304
|
-
},
|
|
13282
|
+
...NON_INTERACTIVE_ROOT_ARGS,
|
|
13305
13283
|
...REGISTRY_CLI_ARGS
|
|
13306
13284
|
},
|
|
13307
13285
|
async run({ args }) {
|
|
@@ -13429,7 +13407,7 @@ ${err.recovery}
|
|
|
13429
13407
|
|
|
13430
13408
|
// src/commands/validate.ts
|
|
13431
13409
|
import { readFile as readFile30 } from "fs/promises";
|
|
13432
|
-
import
|
|
13410
|
+
import path56 from "path";
|
|
13433
13411
|
import { defineCommand as defineCommand22 } from "citty";
|
|
13434
13412
|
|
|
13435
13413
|
// src/invariants/plan-schema.ts
|
|
@@ -13476,7 +13454,7 @@ function validatePlanFrontmatterText(text) {
|
|
|
13476
13454
|
// src/commands/validate.ts
|
|
13477
13455
|
async function resolveEditedPath(cwd, explicit) {
|
|
13478
13456
|
if (explicit) {
|
|
13479
|
-
const filePath2 =
|
|
13457
|
+
const filePath2 = path56.resolve(cwd, explicit);
|
|
13480
13458
|
try {
|
|
13481
13459
|
return { filePath: filePath2, content: await readFile30(filePath2, "utf8") };
|
|
13482
13460
|
} catch {
|
|
@@ -13486,7 +13464,7 @@ async function resolveEditedPath(cwd, explicit) {
|
|
|
13486
13464
|
const payload = await readStdinJson();
|
|
13487
13465
|
const rel = typeof payload.file_path === "string" && payload.file_path || typeof payload.path === "string" && payload.path || typeof payload.file === "string" && payload.file || "";
|
|
13488
13466
|
if (!rel) return null;
|
|
13489
|
-
const filePath =
|
|
13467
|
+
const filePath = path56.isAbsolute(rel) ? rel : path56.resolve(cwd, rel);
|
|
13490
13468
|
try {
|
|
13491
13469
|
return { filePath, content: await readFile30(filePath, "utf8") };
|
|
13492
13470
|
} catch {
|
|
@@ -13516,7 +13494,7 @@ var validateCommand = defineCommand22({
|
|
|
13516
13494
|
async run({ args }) {
|
|
13517
13495
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
13518
13496
|
const fileArg = typeof args.file === "string" ? args.file : void 0;
|
|
13519
|
-
const filePath = fileArg ?
|
|
13497
|
+
const filePath = fileArg ? path56.resolve(cwd, fileArg) : path56.join(path56.resolve(cwd), ".cursor", "HANDOFF.md");
|
|
13520
13498
|
let content = "";
|
|
13521
13499
|
try {
|
|
13522
13500
|
content = await readFile30(filePath, "utf8");
|
|
@@ -13549,7 +13527,7 @@ var validateCommand = defineCommand22({
|
|
|
13549
13527
|
process.exitCode = 2;
|
|
13550
13528
|
return;
|
|
13551
13529
|
}
|
|
13552
|
-
const filePath =
|
|
13530
|
+
const filePath = path56.resolve(cwd, fileArg);
|
|
13553
13531
|
let content = "";
|
|
13554
13532
|
try {
|
|
13555
13533
|
content = await readFile30(filePath, "utf8");
|
|
@@ -13571,7 +13549,7 @@ var validateCommand = defineCommand22({
|
|
|
13571
13549
|
},
|
|
13572
13550
|
async run({ args }) {
|
|
13573
13551
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
13574
|
-
const resolved = await resolveEditedPath(
|
|
13552
|
+
const resolved = await resolveEditedPath(path56.resolve(cwd));
|
|
13575
13553
|
if (!resolved) {
|
|
13576
13554
|
console.log(JSON.stringify({}));
|
|
13577
13555
|
return;
|