@dadado/agent-kit-cli 4.8.4 → 4.8.9
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/README.md +59 -0
- package/dashboard/dashboard-data.mjs +235 -77
- package/dashboard/dashboard.html +1505 -468
- package/dashboard/lib/semantic-model.mjs +173 -22
- package/dashboard/lib/terminal-snapshot.mjs +127 -0
- package/dist/index.js +1190 -333
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { defineCommand as
|
|
4
|
+
import { defineCommand as defineCommand19, runMain } from "citty";
|
|
5
5
|
|
|
6
6
|
// src/commands/add.ts
|
|
7
7
|
import { defineCommand } from "citty";
|
|
8
8
|
|
|
9
9
|
// src/lifecycle/apply.ts
|
|
10
|
-
import { copyFile, mkdir as
|
|
11
|
-
import
|
|
10
|
+
import { copyFile, mkdir as mkdir3, readFile as readFile3 } from "fs/promises";
|
|
11
|
+
import path4 from "path";
|
|
12
12
|
|
|
13
13
|
// src/manifest/types.ts
|
|
14
14
|
var MANIFEST_FILENAME = "agent-kit.json";
|
|
@@ -62,6 +62,62 @@ async function listDirectory(rootDir) {
|
|
|
62
62
|
return readdir(rootDir);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// src/lifecycle/overlay.ts
|
|
66
|
+
import { createHash } from "crypto";
|
|
67
|
+
import { mkdir as mkdir2, readFile as readFile2, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
|
|
68
|
+
import path3 from "path";
|
|
69
|
+
|
|
70
|
+
// src/lifecycle/overlay-known-hashes.ts
|
|
71
|
+
var KNOWN_SHIPPED_OVERLAY_HASHES = /* @__PURE__ */ new Set([
|
|
72
|
+
"082bdbc584be3f3d8aa6e9d7b9f4e076450f3ff48f9147a1deb557e31475036b",
|
|
73
|
+
"15566765ec95dad3bc3160f20ac910e01a13471df536e6d990f4b263700784a9",
|
|
74
|
+
"1681e6cf7b80a67cd999b94b8b6e16eb8caed0e11b32905848ba733da956e902",
|
|
75
|
+
"1974a5af0e4ba2bb0fc7e1872ef9ab70e323323ab216958c31731bcdd82c8241",
|
|
76
|
+
"1e58e0d4f2e8459b95a40d1b1e72c5c85a87230cbde945840a84073fe3a20422",
|
|
77
|
+
"285668d08f1b15d96286d27e29cc4a67e126e097fb8a940bdde33ccf25bd2bcd",
|
|
78
|
+
"3f3354825ff06ff9f9ae1e29b71784fef4250020c23c1b490940cf06b2be0f28",
|
|
79
|
+
"4bf23acbc9bd4e0f8468e61f4970e182600b72d9025d29e81ddbd83cb7b1710a",
|
|
80
|
+
"4c5167d0ebde685f266dce32c516a7c43876e0520647b93f07f2e2147e6a7b2f",
|
|
81
|
+
"4dc4efcd1b43c643d3889fb349df8d4964d0867c3a67eb67e2ec168fbb70fc75",
|
|
82
|
+
"4ff844cf789cef4b1e809a55ca006316cd964bcdb2557d91c916a08e30ad15f6",
|
|
83
|
+
"533b41599c25698e5746166096a68fd4080efb5e16287b2a1ed5f73295cda446",
|
|
84
|
+
"570c3f91fd31a4cc1e56e997c85feab5c6756f45fc73dc741afe573912111231",
|
|
85
|
+
"5d302d207521c53b9fa814ee0eba1376a5b2bfd2983c0373657c9fdf9884541b",
|
|
86
|
+
"621bc6cecf7c710a97e93463ca9b8872a1fcab1325c358cc8be65b418eebb339",
|
|
87
|
+
"69715048da3963947aa8a39e7ac30ec01632fec862bfcb9559c46764b4833be0",
|
|
88
|
+
"6a42e76fc038ca69ddad18541b8df161770c58980fefaf9e5e80d87407074bec",
|
|
89
|
+
"6a9af9e8a95dbb279347166c43dcee245ca673f9687841086b49911c54944c26",
|
|
90
|
+
"6b66ed7747ea2d19cb457f5af656e3c07f9f563f7f32b3951fa4ae5f27f0ac46",
|
|
91
|
+
"76ae85bbc336c6f435d9653e8f254ae49774b4ebaecf7335697115933eb8797c",
|
|
92
|
+
"8108b202c829627c3c2df33575016ebe942df42acf4f1cc393900be3b82b402c",
|
|
93
|
+
"8619500b0c7024390d43e3aaefa2a2d731728d7c664aaf90e0acdd067f3397ce",
|
|
94
|
+
"8a11c80b0fbf6c4aabf60eec05b8cca7dccf5bca472787a8afa3028fd8fbedfa",
|
|
95
|
+
"8b25659727063013e8bf6d7925f815b2630c2919b21e6fdac354362bf122bfab",
|
|
96
|
+
"8c7edbacc74b1431fbecb04c9857c0e7a9cb1578fc6ca6367be888dbcec3c98f",
|
|
97
|
+
"9439da963acbe5dd88a3aa4d6ba4fb297db666ce8f2c9d3917dbc6d6e517f9aa",
|
|
98
|
+
"98d96e25f67aa9c234cb0c422f0d701c22a04bfe184fb4eb68295003d429552b",
|
|
99
|
+
"99031145609af46e7e0f315006bf633f47002eccece8eb7af8b5efd762313ade",
|
|
100
|
+
"9a3cea681399b42ef930a0720802378fe1b06ee73370df9ede9d0309c3c60ab2",
|
|
101
|
+
"9d905aca0123f3018c81c579217104f1749d8da802f3837b2407fbad8f2d803f",
|
|
102
|
+
"a08d04f8efa121ad498f96873778c22630c9172c07c216839e348cd0bb7c2292",
|
|
103
|
+
"b5c7a10df90d209be07159b7ff9bfa24e869538a0b6026bb5ce79e1cdd5b2528",
|
|
104
|
+
"c96d522be440fbb638dbe953cebf1bb31185c431ac2e14446369cfb7d155a951",
|
|
105
|
+
"cf2bd11891b934484171fa705f470de284e3874402e1e487750735dbc7692857",
|
|
106
|
+
"d4659e4bb6fd6022559fc632b17db6a39007c04bc0ded59017a0dacc013632ed",
|
|
107
|
+
"d484243167e13c34f9df7ff378baf2967f7cbfcd88170d4d32e745efbcae7480",
|
|
108
|
+
"d96aa15dde45147aa40db64684688a4b5c9ad660dcd16c55156b31933a42c37e",
|
|
109
|
+
"d9ecd90f80ebc25db73ebed27c32596b9cfce518b3026a3a4d5787321ba6324a",
|
|
110
|
+
"dd01d051dbab257e16609b02c983bad6a576ed8473781b4f1df9ef2eb5f85795",
|
|
111
|
+
"de6efabde6a1d69357926b88b5a4ed3eec19e11b82597cb999f5365f952305b8",
|
|
112
|
+
"e03b5764b908f9ed5c592108fbc11d5962d9256fd129a63ab4a39c8efaa2ef3e",
|
|
113
|
+
"e102e77f9f520f938d26861978249e4d85a8c8755d48c8e79b21fef9423325ee",
|
|
114
|
+
"e511e04064ffb8c429b659d2ae2e0f223e8cc51039ae816753095a5f7a160595",
|
|
115
|
+
"ebd41f8df764a14b3460016fafcfdde98c4c3a59a3c17f773451ef061fc42f95",
|
|
116
|
+
"f061432a2606049717c0cbf098ef07b1da4df2b136c4cb9cdaa7ea44c876bcd6",
|
|
117
|
+
"f061d36a1180293519fbb560bbf659c621bd90885e995fca6eaf2eda9e418a50",
|
|
118
|
+
"f28731a82f4b394a5765fdefe2d1b667810831f6dcea400ca62bc66e1d65371c"
|
|
119
|
+
]);
|
|
120
|
+
|
|
65
121
|
// src/lifecycle/paths.ts
|
|
66
122
|
import path2 from "path";
|
|
67
123
|
function resolveContained(root, rel) {
|
|
@@ -77,6 +133,77 @@ function toPosixRel(root, absPath) {
|
|
|
77
133
|
return path2.relative(root, absPath).split(path2.sep).join("/");
|
|
78
134
|
}
|
|
79
135
|
|
|
136
|
+
// src/lifecycle/overlay.ts
|
|
137
|
+
var MANAGED_HASHES_REL = ".cursor/agent-kit.managed-hashes.json";
|
|
138
|
+
var CONSUMER_OVERLAY_PREFIXES = [
|
|
139
|
+
".cursor/agents/",
|
|
140
|
+
".cursor/skills/",
|
|
141
|
+
".cursor/commands/"
|
|
142
|
+
];
|
|
143
|
+
function isConsumerOverlayPath(relPath) {
|
|
144
|
+
const norm = relPath.split(path3.sep).join("/");
|
|
145
|
+
return CONSUMER_OVERLAY_PREFIXES.some((p) => norm.startsWith(p));
|
|
146
|
+
}
|
|
147
|
+
function contentHash(content) {
|
|
148
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
149
|
+
}
|
|
150
|
+
async function loadManagedHashLedger(projectRoot) {
|
|
151
|
+
const abs = resolveContained(projectRoot, MANAGED_HASHES_REL);
|
|
152
|
+
try {
|
|
153
|
+
const raw = await readFile2(abs, "utf8");
|
|
154
|
+
const parsed = JSON.parse(raw);
|
|
155
|
+
if (parsed && typeof parsed === "object" && parsed.hashes && typeof parsed.hashes === "object") {
|
|
156
|
+
return { schemaVersion: 1, hashes: { ...parsed.hashes } };
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
160
|
+
return { schemaVersion: 1, hashes: {} };
|
|
161
|
+
}
|
|
162
|
+
async function saveManagedHashLedger(projectRoot, ledger) {
|
|
163
|
+
const abs = resolveContained(projectRoot, MANAGED_HASHES_REL);
|
|
164
|
+
await mkdir2(path3.dirname(abs), { recursive: true });
|
|
165
|
+
const payload = {
|
|
166
|
+
schemaVersion: 1,
|
|
167
|
+
hashes: { ...ledger.hashes }
|
|
168
|
+
};
|
|
169
|
+
await writeFile2(abs, `${JSON.stringify(payload, null, 2)}
|
|
170
|
+
`, "utf8");
|
|
171
|
+
}
|
|
172
|
+
function shouldPreserveCustomizedOverlay(localContent, recordedHash, knownShippedHashes = KNOWN_SHIPPED_OVERLAY_HASHES) {
|
|
173
|
+
const localHash = contentHash(localContent);
|
|
174
|
+
if (!recordedHash) {
|
|
175
|
+
return !knownShippedHashes.has(localHash);
|
|
176
|
+
}
|
|
177
|
+
return localHash !== recordedHash;
|
|
178
|
+
}
|
|
179
|
+
async function seedManagedHashLedger(projectRoot) {
|
|
180
|
+
const ledger = await loadManagedHashLedger(projectRoot);
|
|
181
|
+
for (const prefix of CONSUMER_OVERLAY_PREFIXES) {
|
|
182
|
+
const prefixPath = resolveContained(projectRoot, prefix);
|
|
183
|
+
let entries;
|
|
184
|
+
try {
|
|
185
|
+
entries = await readdir2(prefixPath, { withFileTypes: true, recursive: true });
|
|
186
|
+
} catch {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
for (const entry of entries) {
|
|
190
|
+
if (!entry.isFile()) continue;
|
|
191
|
+
const parentPath = String(entry.parentPath);
|
|
192
|
+
const name = String(entry.name);
|
|
193
|
+
const rel = path3.relative(projectRoot, path3.join(parentPath, name));
|
|
194
|
+
const norm = rel.split(path3.sep).join("/");
|
|
195
|
+
if (ledger.hashes[norm]) continue;
|
|
196
|
+
try {
|
|
197
|
+
const content = await readFile2(path3.join(parentPath, name), "utf8");
|
|
198
|
+
ledger.hashes[norm] = contentHash(content);
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
await saveManagedHashLedger(projectRoot, ledger);
|
|
204
|
+
return ledger;
|
|
205
|
+
}
|
|
206
|
+
|
|
80
207
|
// src/lifecycle/glob.ts
|
|
81
208
|
function normalizeRelPath(input) {
|
|
82
209
|
return input.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/");
|
|
@@ -158,7 +285,8 @@ function emptyStats() {
|
|
|
158
285
|
collisions: [],
|
|
159
286
|
skippedProtected: [],
|
|
160
287
|
missing: [],
|
|
161
|
-
unchanged: []
|
|
288
|
+
unchanged: [],
|
|
289
|
+
preservedCustomized: []
|
|
162
290
|
};
|
|
163
291
|
}
|
|
164
292
|
function mergeStats(into, from) {
|
|
@@ -168,34 +296,82 @@ function mergeStats(into, from) {
|
|
|
168
296
|
into.skippedProtected.push(...from.skippedProtected);
|
|
169
297
|
into.missing.push(...from.missing);
|
|
170
298
|
into.unchanged.push(...from.unchanged);
|
|
299
|
+
into.preservedCustomized.push(...from.preservedCustomized);
|
|
171
300
|
return into;
|
|
172
301
|
}
|
|
173
|
-
async function copyRegistryFile(registryRoot, projectRoot, sourceRel, targetRel, protectedGlobs) {
|
|
174
|
-
const targetNorm = targetRel.split(
|
|
302
|
+
async function copyRegistryFile(registryRoot, projectRoot, sourceRel, targetRel, protectedGlobs, options = {}) {
|
|
303
|
+
const targetNorm = targetRel.split(path4.sep).join("/");
|
|
175
304
|
if (isProtectedPath(targetNorm, protectedGlobs)) {
|
|
176
305
|
return "skipped-protected";
|
|
177
306
|
}
|
|
178
307
|
const sourceAbs = resolveContained(registryRoot, sourceRel);
|
|
179
308
|
const targetAbs = resolveContained(projectRoot, targetRel);
|
|
180
309
|
try {
|
|
181
|
-
await
|
|
310
|
+
await readFile3(sourceAbs);
|
|
182
311
|
} catch {
|
|
183
312
|
return "missing-source";
|
|
184
313
|
}
|
|
185
314
|
let existing = null;
|
|
186
315
|
try {
|
|
187
|
-
existing = await
|
|
316
|
+
existing = await readFile3(targetAbs, "utf8");
|
|
188
317
|
} catch {
|
|
189
318
|
existing = null;
|
|
190
319
|
}
|
|
191
|
-
const next = await
|
|
192
|
-
if (existing === next)
|
|
193
|
-
|
|
320
|
+
const next = await readFile3(sourceAbs, "utf8");
|
|
321
|
+
if (existing === next) {
|
|
322
|
+
if (isConsumerOverlayPath(targetNorm)) {
|
|
323
|
+
await touchOverlayHash(projectRoot, targetNorm, next, options);
|
|
324
|
+
}
|
|
325
|
+
return "unchanged";
|
|
326
|
+
}
|
|
327
|
+
if (existing !== null && isConsumerOverlayPath(targetNorm)) {
|
|
328
|
+
const ledger = options.managedHashes ?? await loadManagedHashLedger(projectRoot);
|
|
329
|
+
const recorded = ledger.hashes[targetNorm];
|
|
330
|
+
if (shouldPreserveCustomizedOverlay(existing, recorded)) {
|
|
331
|
+
if (!recorded) {
|
|
332
|
+
const managedHash = contentHash(next);
|
|
333
|
+
ledger.hashes[targetNorm] = managedHash;
|
|
334
|
+
if (options.managedHashes) {
|
|
335
|
+
options.managedHashes.hashes[targetNorm] = managedHash;
|
|
336
|
+
}
|
|
337
|
+
if (options.persistManagedHashes !== false) {
|
|
338
|
+
await saveManagedHashLedger(projectRoot, ledger);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return "preserved-customized";
|
|
342
|
+
}
|
|
343
|
+
await mkdir3(path4.dirname(targetAbs), { recursive: true });
|
|
344
|
+
await copyFile(sourceAbs, targetAbs);
|
|
345
|
+
ledger.hashes[targetNorm] = contentHash(next);
|
|
346
|
+
if (options.managedHashes) {
|
|
347
|
+
options.managedHashes.hashes[targetNorm] = ledger.hashes[targetNorm];
|
|
348
|
+
}
|
|
349
|
+
if (options.persistManagedHashes !== false) {
|
|
350
|
+
await saveManagedHashLedger(projectRoot, ledger);
|
|
351
|
+
}
|
|
352
|
+
return "written";
|
|
353
|
+
}
|
|
354
|
+
await mkdir3(path4.dirname(targetAbs), { recursive: true });
|
|
194
355
|
await copyFile(sourceAbs, targetAbs);
|
|
356
|
+
if (isConsumerOverlayPath(targetNorm)) {
|
|
357
|
+
await touchOverlayHash(projectRoot, targetNorm, next, options);
|
|
358
|
+
}
|
|
195
359
|
return "written";
|
|
196
360
|
}
|
|
361
|
+
async function touchOverlayHash(projectRoot, targetNorm, content, options) {
|
|
362
|
+
const ledger = options.managedHashes ?? await loadManagedHashLedger(projectRoot);
|
|
363
|
+
const hash = contentHash(content);
|
|
364
|
+
if (ledger.hashes[targetNorm] === hash) return;
|
|
365
|
+
ledger.hashes[targetNorm] = hash;
|
|
366
|
+
if (options.managedHashes) {
|
|
367
|
+
options.managedHashes.hashes[targetNorm] = hash;
|
|
368
|
+
}
|
|
369
|
+
if (options.persistManagedHashes !== false) {
|
|
370
|
+
await saveManagedHashLedger(projectRoot, ledger);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
197
373
|
function recordOutcome(stats, targetRel, outcome) {
|
|
198
|
-
const rel = targetRel.split(
|
|
374
|
+
const rel = targetRel.split(path4.sep).join("/");
|
|
199
375
|
switch (outcome) {
|
|
200
376
|
case "written":
|
|
201
377
|
stats.written.push(rel);
|
|
@@ -209,14 +385,19 @@ function recordOutcome(stats, targetRel, outcome) {
|
|
|
209
385
|
case "unchanged":
|
|
210
386
|
stats.unchanged.push(rel);
|
|
211
387
|
break;
|
|
388
|
+
case "preserved-customized":
|
|
389
|
+
stats.preservedCustomized.push(rel);
|
|
390
|
+
break;
|
|
212
391
|
}
|
|
213
392
|
}
|
|
214
393
|
async function saveManifest(projectRoot, manifest) {
|
|
215
|
-
const target =
|
|
394
|
+
const target = path4.join(projectRoot, MANIFEST_RELATIVE_PATH);
|
|
216
395
|
const payload = {
|
|
217
396
|
...manifest,
|
|
218
397
|
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
219
|
-
installedAt
|
|
398
|
+
// Preserve installedAt on no-op updates (ADR factory-pseudo-consumer
|
|
399
|
+
// decision 4); a version change earns a fresh install timestamp.
|
|
400
|
+
installedAt: manifest.installedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
220
401
|
};
|
|
221
402
|
await writeJson(target, payload);
|
|
222
403
|
return toPosixRel(projectRoot, target);
|
|
@@ -276,6 +457,12 @@ function logApplyStats(stats) {
|
|
|
276
457
|
logger.warn("Slash collision preserved because the legacy command is customized:");
|
|
277
458
|
for (const p of stats.collisions) logger.info(` ! ${p}`);
|
|
278
459
|
}
|
|
460
|
+
if (stats.preservedCustomized.length > 0) {
|
|
461
|
+
logger.warn(
|
|
462
|
+
`Preserved customized overlay (agents/skills/commands); run diff / contribute to sync upstream: ${stats.preservedCustomized.length}`
|
|
463
|
+
);
|
|
464
|
+
for (const p of stats.preservedCustomized) logger.info(` ! ${p}`);
|
|
465
|
+
}
|
|
279
466
|
if (stats.skippedProtected.length > 0) {
|
|
280
467
|
logger.warn(`Skipped protected (L3): ${stats.skippedProtected.length}`);
|
|
281
468
|
for (const p of stats.skippedProtected) logger.info(` ~ ${p}`);
|
|
@@ -288,9 +475,9 @@ function logApplyStats(stats) {
|
|
|
288
475
|
|
|
289
476
|
// src/registry/resolve.ts
|
|
290
477
|
import { execFile } from "child_process";
|
|
291
|
-
import { createHash } from "crypto";
|
|
478
|
+
import { createHash as createHash2 } from "crypto";
|
|
292
479
|
import { homedir } from "os";
|
|
293
|
-
import
|
|
480
|
+
import path5 from "path";
|
|
294
481
|
import { promisify } from "util";
|
|
295
482
|
var execFileAsync = promisify(execFile);
|
|
296
483
|
var DEFAULT_REGISTRY_URL = "https://github.com/agent-kit-startup/agent-kit";
|
|
@@ -311,14 +498,14 @@ function gitEnv() {
|
|
|
311
498
|
return { ...process.env, GIT_ALLOW_PROTOCOL: "https" };
|
|
312
499
|
}
|
|
313
500
|
function cacheKey(url, ref) {
|
|
314
|
-
return
|
|
501
|
+
return createHash2("sha256").update(`${url}@${ref}`).digest("hex").slice(0, 16);
|
|
315
502
|
}
|
|
316
503
|
async function hasRegistryIndex(root) {
|
|
317
|
-
return fileExists(
|
|
504
|
+
return fileExists(path5.join(root, "registry", "registry.json"));
|
|
318
505
|
}
|
|
319
506
|
async function cloneRegistry(url, ref, dest) {
|
|
320
|
-
const { mkdir:
|
|
321
|
-
await
|
|
507
|
+
const { mkdir: mkdir6 } = await import("fs/promises");
|
|
508
|
+
await mkdir6(path5.dirname(dest), { recursive: true });
|
|
322
509
|
try {
|
|
323
510
|
await execFileAsync("git", ["clone", "--depth", "1", "--branch", ref, "--", url, dest], {
|
|
324
511
|
env: gitEnv()
|
|
@@ -343,7 +530,7 @@ async function refreshCache(cacheDir) {
|
|
|
343
530
|
}
|
|
344
531
|
async function resolveRegistryRoot(options) {
|
|
345
532
|
if (options.registryPath) {
|
|
346
|
-
const root =
|
|
533
|
+
const root = path5.resolve(options.registryPath);
|
|
347
534
|
if (!await hasRegistryIndex(root)) {
|
|
348
535
|
throw new Error(`No registry/registry.json under --registry ${root}`);
|
|
349
536
|
}
|
|
@@ -351,7 +538,7 @@ async function resolveRegistryRoot(options) {
|
|
|
351
538
|
}
|
|
352
539
|
const envPath = process.env.AGENT_KIT_REGISTRY;
|
|
353
540
|
if (envPath) {
|
|
354
|
-
const root =
|
|
541
|
+
const root = path5.resolve(envPath);
|
|
355
542
|
if (!await hasRegistryIndex(root)) {
|
|
356
543
|
throw new Error(`No registry/registry.json under AGENT_KIT_REGISTRY=${root}`);
|
|
357
544
|
}
|
|
@@ -359,7 +546,7 @@ async function resolveRegistryRoot(options) {
|
|
|
359
546
|
}
|
|
360
547
|
if (await hasRegistryIndex(options.cwd)) {
|
|
361
548
|
return {
|
|
362
|
-
root:
|
|
549
|
+
root: path5.resolve(options.cwd),
|
|
363
550
|
source: "cwd",
|
|
364
551
|
url: options.registryUrl,
|
|
365
552
|
ref: options.registryRef
|
|
@@ -368,7 +555,7 @@ async function resolveRegistryRoot(options) {
|
|
|
368
555
|
const url = options.registryUrl ?? DEFAULT_REGISTRY_URL;
|
|
369
556
|
const ref = options.registryRef ?? DEFAULT_REGISTRY_REF;
|
|
370
557
|
assertSafeRegistrySource(url, ref);
|
|
371
|
-
const dest =
|
|
558
|
+
const dest = path5.join(homedir(), ".cache", "agent-kit", "registry", cacheKey(url, ref));
|
|
372
559
|
if (await hasRegistryIndex(dest)) {
|
|
373
560
|
await refreshCache(dest);
|
|
374
561
|
return { root: dest, source: "remote-cache", url, ref };
|
|
@@ -429,7 +616,7 @@ function loadPackageVersion() {
|
|
|
429
616
|
var KIT_VERSION = loadPackageVersion();
|
|
430
617
|
|
|
431
618
|
// src/manifest/index.ts
|
|
432
|
-
import
|
|
619
|
+
import path6 from "path";
|
|
433
620
|
|
|
434
621
|
// src/manifest/parse.ts
|
|
435
622
|
var SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
@@ -605,16 +792,16 @@ function parseAgentKitManifest(raw) {
|
|
|
605
792
|
|
|
606
793
|
// src/manifest/index.ts
|
|
607
794
|
async function loadAgentKitManifest(cwd) {
|
|
608
|
-
const target =
|
|
795
|
+
const target = path6.join(cwd, MANIFEST_RELATIVE_PATH);
|
|
609
796
|
const raw = await readJson(target);
|
|
610
797
|
if (raw === null) return null;
|
|
611
798
|
return parseAgentKitManifest(raw);
|
|
612
799
|
}
|
|
613
800
|
|
|
614
801
|
// src/registry/client.ts
|
|
615
|
-
import
|
|
802
|
+
import path7 from "path";
|
|
616
803
|
async function loadRegistry(rootDir) {
|
|
617
|
-
const indexPath =
|
|
804
|
+
const indexPath = path7.join(rootDir, "registry", "registry.json");
|
|
618
805
|
const index = await readJson(indexPath);
|
|
619
806
|
if (!index) throw new Error(`Registry not found at ${indexPath}`);
|
|
620
807
|
return index;
|
|
@@ -639,47 +826,47 @@ async function findPack(rootDir, packId) {
|
|
|
639
826
|
}
|
|
640
827
|
|
|
641
828
|
// src/registry/install.ts
|
|
642
|
-
import { readFile as
|
|
643
|
-
import
|
|
829
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
830
|
+
import path8 from "path";
|
|
644
831
|
function targetForMember(member) {
|
|
645
832
|
switch (member.kind) {
|
|
646
833
|
case "skill": {
|
|
647
834
|
const category = member.source.includes("/core/") ? "core" : "community";
|
|
648
835
|
return {
|
|
649
|
-
sourceRel:
|
|
650
|
-
targetRel:
|
|
836
|
+
sourceRel: path8.posix.join(member.source, "SKILL.md"),
|
|
837
|
+
targetRel: path8.posix.join(".cursor", "skills", category, member.id, "SKILL.md")
|
|
651
838
|
};
|
|
652
839
|
}
|
|
653
840
|
case "rule":
|
|
654
841
|
return {
|
|
655
842
|
sourceRel: member.source,
|
|
656
|
-
targetRel:
|
|
843
|
+
targetRel: path8.posix.join(".cursor", "rules", path8.posix.basename(member.source))
|
|
657
844
|
};
|
|
658
845
|
case "agent":
|
|
659
846
|
return {
|
|
660
847
|
sourceRel: member.source,
|
|
661
|
-
targetRel:
|
|
848
|
+
targetRel: path8.posix.join(".cursor", "agents", path8.posix.basename(member.source))
|
|
662
849
|
};
|
|
663
850
|
case "command":
|
|
664
851
|
return {
|
|
665
852
|
sourceRel: member.source,
|
|
666
|
-
targetRel:
|
|
853
|
+
targetRel: path8.posix.join(".cursor", "commands", path8.posix.basename(member.source))
|
|
667
854
|
};
|
|
668
855
|
case "hook": {
|
|
669
|
-
const targetRel = member.source.startsWith(".cursor/") ? member.source :
|
|
856
|
+
const targetRel = member.source.startsWith(".cursor/") ? member.source : path8.posix.join(".cursor", "hooks", path8.posix.basename(member.source));
|
|
670
857
|
return { sourceRel: member.source, targetRel };
|
|
671
858
|
}
|
|
672
859
|
case "template": {
|
|
673
|
-
const base =
|
|
860
|
+
const base = path8.posix.basename(member.source);
|
|
674
861
|
if (member.source.includes("/context-management/") || member.source.includes("/engineering-architecture/")) {
|
|
675
862
|
return {
|
|
676
863
|
sourceRel: member.source,
|
|
677
|
-
targetRel:
|
|
864
|
+
targetRel: path8.posix.join(".cursor", "context", "templates", base)
|
|
678
865
|
};
|
|
679
866
|
}
|
|
680
867
|
return {
|
|
681
868
|
sourceRel: member.source,
|
|
682
|
-
targetRel:
|
|
869
|
+
targetRel: path8.posix.join("templates", base)
|
|
683
870
|
};
|
|
684
871
|
}
|
|
685
872
|
default: {
|
|
@@ -691,20 +878,23 @@ function targetForMember(member) {
|
|
|
691
878
|
async function installSkill(registryRoot, projectRoot, skill, options = {}) {
|
|
692
879
|
const stats = emptyStats();
|
|
693
880
|
const category = skill.path.includes("/core/") ? "core" : "community";
|
|
694
|
-
const sourceRel =
|
|
695
|
-
const targetRel =
|
|
881
|
+
const sourceRel = path8.posix.join(skill.path, "SKILL.md");
|
|
882
|
+
const targetRel = path8.posix.join(".cursor", "skills", category, skill.id, "SKILL.md");
|
|
883
|
+
const managedHashes = await loadManagedHashLedger(projectRoot);
|
|
696
884
|
const outcome = await copyRegistryFile(
|
|
697
885
|
registryRoot,
|
|
698
886
|
projectRoot,
|
|
699
887
|
sourceRel,
|
|
700
888
|
targetRel,
|
|
701
|
-
options.protectedGlobs ?? []
|
|
889
|
+
options.protectedGlobs ?? [],
|
|
890
|
+
{ managedHashes, persistManagedHashes: false }
|
|
702
891
|
);
|
|
703
892
|
recordOutcome(stats, targetRel, outcome);
|
|
893
|
+
await saveManagedHashLedger(projectRoot, managedHashes);
|
|
704
894
|
return stats;
|
|
705
895
|
}
|
|
706
896
|
async function installSkillsByIds(registryRoot, projectRoot, skillIds, options = {}) {
|
|
707
|
-
const index = await readJson(
|
|
897
|
+
const index = await readJson(path8.join(registryRoot, "registry", "registry.json"));
|
|
708
898
|
const stats = emptyStats();
|
|
709
899
|
if (!index) return stats;
|
|
710
900
|
const pool = allSkills(index);
|
|
@@ -720,7 +910,7 @@ async function installSkillsByIds(registryRoot, projectRoot, skillIds, options =
|
|
|
720
910
|
}
|
|
721
911
|
async function loadPackManifest(registryRoot, packId) {
|
|
722
912
|
const summary = await findPack(registryRoot, packId);
|
|
723
|
-
const manifestPath = resolveContained(registryRoot,
|
|
913
|
+
const manifestPath = resolveContained(registryRoot, path8.join(summary.path, "pack.json"));
|
|
724
914
|
const manifest = await readJson(manifestPath);
|
|
725
915
|
if (!manifest) throw new Error(`Pack manifest not found at ${manifestPath}`);
|
|
726
916
|
return manifest;
|
|
@@ -730,6 +920,8 @@ async function installPack(registryRoot, projectRoot, packId, options = {}) {
|
|
|
730
920
|
const packManifest = await loadPackManifest(registryRoot, packId);
|
|
731
921
|
const stats = emptyStats();
|
|
732
922
|
const protectedGlobs = options.protectedGlobs ?? [];
|
|
923
|
+
const managedHashes = await loadManagedHashLedger(projectRoot);
|
|
924
|
+
const copyOpts = { managedHashes, persistManagedHashes: false };
|
|
733
925
|
for (const member of packManifest.members) {
|
|
734
926
|
const { sourceRel, targetRel } = targetForMember(member);
|
|
735
927
|
const outcome = await copyRegistryFile(
|
|
@@ -737,10 +929,12 @@ async function installPack(registryRoot, projectRoot, packId, options = {}) {
|
|
|
737
929
|
projectRoot,
|
|
738
930
|
sourceRel,
|
|
739
931
|
targetRel,
|
|
740
|
-
protectedGlobs
|
|
932
|
+
protectedGlobs,
|
|
933
|
+
copyOpts
|
|
741
934
|
);
|
|
742
935
|
recordOutcome(stats, targetRel, outcome);
|
|
743
936
|
}
|
|
937
|
+
await saveManagedHashLedger(projectRoot, managedHashes);
|
|
744
938
|
return stats;
|
|
745
939
|
}
|
|
746
940
|
function packMemberTargets(member) {
|
|
@@ -853,8 +1047,8 @@ var addCommand = defineCommand({
|
|
|
853
1047
|
import { defineCommand as defineCommand2 } from "citty";
|
|
854
1048
|
|
|
855
1049
|
// src/lifecycle/contribute.ts
|
|
856
|
-
import { mkdir as
|
|
857
|
-
import
|
|
1050
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
|
|
1051
|
+
import path9 from "path";
|
|
858
1052
|
|
|
859
1053
|
// src/lifecycle/contribute-gate.ts
|
|
860
1054
|
var SECRET_PATTERNS = [
|
|
@@ -1042,6 +1236,14 @@ var L0_ARTIFACTS = [
|
|
|
1042
1236
|
source: ".cursor/commands/field-report-resolve.md",
|
|
1043
1237
|
target: ".cursor/commands/field-report-resolve.md"
|
|
1044
1238
|
},
|
|
1239
|
+
{
|
|
1240
|
+
source: ".cursor/commands/dogfood.md",
|
|
1241
|
+
target: ".cursor/commands/dogfood.md"
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
source: ".cursor/commands/cursor-update-awareness.md",
|
|
1245
|
+
target: ".cursor/commands/cursor-update-awareness.md"
|
|
1246
|
+
},
|
|
1045
1247
|
// Context (templates + example config; private config.json is not L0)
|
|
1046
1248
|
{
|
|
1047
1249
|
source: ".cursor/context/templates/plan-external-review-prompt.md",
|
|
@@ -1136,7 +1338,7 @@ var L0_ARTIFACTS = [
|
|
|
1136
1338
|
// src/lifecycle/contribute.ts
|
|
1137
1339
|
async function readLocal(projectRoot, rel) {
|
|
1138
1340
|
try {
|
|
1139
|
-
return await
|
|
1341
|
+
return await readFile5(resolveContained(projectRoot, rel), "utf8");
|
|
1140
1342
|
} catch {
|
|
1141
1343
|
return null;
|
|
1142
1344
|
}
|
|
@@ -1144,13 +1346,13 @@ async function readLocal(projectRoot, rel) {
|
|
|
1144
1346
|
async function buildRegistryPathMap(registryRoot, manifest) {
|
|
1145
1347
|
const map = /* @__PURE__ */ new Map();
|
|
1146
1348
|
for (const a of L0_ARTIFACTS) {
|
|
1147
|
-
map.set(a.target.split(
|
|
1349
|
+
map.set(a.target.split(path9.sep).join("/"), a.source.split(path9.sep).join("/"));
|
|
1148
1350
|
}
|
|
1149
1351
|
for (const packId of manifest.packs ?? []) {
|
|
1150
1352
|
const pack = await loadPackManifest(registryRoot, packId);
|
|
1151
1353
|
for (const member of pack.members) {
|
|
1152
1354
|
const { sourceRel, targetRel } = packMemberTargets(member);
|
|
1153
|
-
map.set(targetRel.split(
|
|
1355
|
+
map.set(targetRel.split(path9.sep).join("/"), sourceRel.split(path9.sep).join("/"));
|
|
1154
1356
|
}
|
|
1155
1357
|
}
|
|
1156
1358
|
if ((manifest.skills ?? []).length > 0) {
|
|
@@ -1160,24 +1362,24 @@ async function buildRegistryPathMap(registryRoot, manifest) {
|
|
|
1160
1362
|
const skill = pool.find((s) => s.id === id);
|
|
1161
1363
|
if (!skill) continue;
|
|
1162
1364
|
const category = skill.path.includes("/core/") ? "core" : "community";
|
|
1163
|
-
const sourceRel =
|
|
1164
|
-
const targetRel =
|
|
1365
|
+
const sourceRel = path9.posix.join(skill.path, "SKILL.md");
|
|
1366
|
+
const targetRel = path9.posix.join(".cursor", "skills", category, skill.id, "SKILL.md");
|
|
1165
1367
|
map.set(targetRel, sourceRel);
|
|
1166
1368
|
}
|
|
1167
1369
|
}
|
|
1168
1370
|
return map;
|
|
1169
1371
|
}
|
|
1170
1372
|
function guessRegistryPath(projectRel) {
|
|
1171
|
-
const p = projectRel.split(
|
|
1373
|
+
const p = projectRel.split(path9.sep).join("/");
|
|
1172
1374
|
if (p.startsWith(".cursor/skills/") && p.endsWith("/SKILL.md")) {
|
|
1173
1375
|
const rest = p.slice(".cursor/skills/".length);
|
|
1174
1376
|
const parts = rest.split("/");
|
|
1175
1377
|
if (parts.length === 3 && (parts[0] === "core" || parts[0] === "community")) {
|
|
1176
|
-
return
|
|
1378
|
+
return path9.posix.join("registry/skills", rest);
|
|
1177
1379
|
}
|
|
1178
1380
|
const skillId = parts[0];
|
|
1179
1381
|
if (parts.length === 2 && skillId) {
|
|
1180
|
-
return
|
|
1382
|
+
return path9.posix.join("registry/skills", "community", skillId, "SKILL.md");
|
|
1181
1383
|
}
|
|
1182
1384
|
}
|
|
1183
1385
|
if (p.startsWith(".cursor/rules/")) {
|
|
@@ -1203,7 +1405,7 @@ async function planContribute(options) {
|
|
|
1203
1405
|
const candidates = [];
|
|
1204
1406
|
const seen = /* @__PURE__ */ new Set();
|
|
1205
1407
|
const push = async (kind, projectPath, registryPath) => {
|
|
1206
|
-
const key = projectPath.split(
|
|
1408
|
+
const key = projectPath.split(path9.sep).join("/");
|
|
1207
1409
|
if (seen.has(key)) return;
|
|
1208
1410
|
seen.add(key);
|
|
1209
1411
|
const content = await readLocal(options.projectRoot, projectPath);
|
|
@@ -1212,7 +1414,7 @@ async function planContribute(options) {
|
|
|
1212
1414
|
}
|
|
1213
1415
|
if (kind === "drift") {
|
|
1214
1416
|
try {
|
|
1215
|
-
const upstream = await
|
|
1417
|
+
const upstream = await readFile5(
|
|
1216
1418
|
resolveContained(options.registryRoot, registryPath),
|
|
1217
1419
|
"utf8"
|
|
1218
1420
|
);
|
|
@@ -1235,7 +1437,7 @@ async function planContribute(options) {
|
|
|
1235
1437
|
}
|
|
1236
1438
|
}
|
|
1237
1439
|
for (const raw of options.extraPaths ?? []) {
|
|
1238
|
-
const projectPath = raw.split(
|
|
1440
|
+
const projectPath = raw.split(path9.sep).join("/");
|
|
1239
1441
|
const blocked = gateContributePath(projectPath);
|
|
1240
1442
|
if (blocked.length > 0) {
|
|
1241
1443
|
if (!seen.has(projectPath)) {
|
|
@@ -1278,10 +1480,10 @@ async function writeContributeToRegistry(projectRoot, registryRoot, accepted) {
|
|
|
1278
1480
|
const written = [];
|
|
1279
1481
|
for (const c of accepted) {
|
|
1280
1482
|
if (!c.registryPath) continue;
|
|
1281
|
-
const content = await
|
|
1483
|
+
const content = await readFile5(resolveContained(projectRoot, c.projectPath), "utf8");
|
|
1282
1484
|
const dest = resolveContained(registryRoot, c.registryPath);
|
|
1283
|
-
await
|
|
1284
|
-
await
|
|
1485
|
+
await mkdir4(path9.dirname(dest), { recursive: true });
|
|
1486
|
+
await writeFile3(dest, content, "utf8");
|
|
1285
1487
|
written.push(c.registryPath);
|
|
1286
1488
|
}
|
|
1287
1489
|
return written;
|
|
@@ -1406,66 +1608,466 @@ var contributeCommand = defineCommand2({
|
|
|
1406
1608
|
}
|
|
1407
1609
|
});
|
|
1408
1610
|
|
|
1611
|
+
// src/commands/cursor-awareness.ts
|
|
1612
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
1613
|
+
|
|
1614
|
+
// src/lifecycle/cursor-update-awareness.ts
|
|
1615
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1616
|
+
import path10 from "path";
|
|
1617
|
+
var DEFAULT_CURSOR_CHANGELOG_URL = "https://cursor.com/changelog";
|
|
1618
|
+
var CHANGELOG_FETCH_TIMEOUT_MS = 15e3;
|
|
1619
|
+
var CURSOR_VERSION_MAJOR_MAX = 20;
|
|
1620
|
+
var INVENTORY_REL = path10.join("docs", "cursor-native-audit.md");
|
|
1621
|
+
var FEATURES_REL = path10.join("docs", "cursor-3-features.md");
|
|
1622
|
+
var DEFAULT_PREFS = {
|
|
1623
|
+
enabled: false,
|
|
1624
|
+
intervalDays: 7,
|
|
1625
|
+
lastCheckedAt: null,
|
|
1626
|
+
lastSeenCursorVersion: null,
|
|
1627
|
+
changelogUrl: DEFAULT_CURSOR_CHANGELOG_URL
|
|
1628
|
+
};
|
|
1629
|
+
var CONVEYOR_HINT = "Confirmed gaps: Ask HITL then `/backlog-add` or `/dogfood` (lane-aware). Never auto Field Reports. Native-audit version prose owned by parked Marketplace plan.";
|
|
1630
|
+
function readCursorUpdateCheckPrefs(config) {
|
|
1631
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
1632
|
+
return { ...DEFAULT_PREFS };
|
|
1633
|
+
}
|
|
1634
|
+
const raw = config.cursorUpdateCheck;
|
|
1635
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1636
|
+
return { ...DEFAULT_PREFS };
|
|
1637
|
+
}
|
|
1638
|
+
const uc = raw;
|
|
1639
|
+
const intervalDays = typeof uc.intervalDays === "number" && Number.isInteger(uc.intervalDays) && uc.intervalDays >= 1 ? uc.intervalDays : DEFAULT_PREFS.intervalDays;
|
|
1640
|
+
const changelogUrl = typeof uc.changelogUrl === "string" && uc.changelogUrl.startsWith("https://") ? uc.changelogUrl : DEFAULT_PREFS.changelogUrl;
|
|
1641
|
+
return {
|
|
1642
|
+
enabled: uc.enabled === true,
|
|
1643
|
+
intervalDays,
|
|
1644
|
+
lastCheckedAt: typeof uc.lastCheckedAt === "string" ? uc.lastCheckedAt : null,
|
|
1645
|
+
lastSeenCursorVersion: typeof uc.lastSeenCursorVersion === "string" ? uc.lastSeenCursorVersion : null,
|
|
1646
|
+
changelogUrl
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
function intervalElapsed(lastCheckedAt, intervalDays) {
|
|
1650
|
+
if (!lastCheckedAt) return true;
|
|
1651
|
+
const last = Date.parse(lastCheckedAt);
|
|
1652
|
+
if (Number.isNaN(last)) return true;
|
|
1653
|
+
return Date.now() - last >= intervalDays * 24 * 60 * 60 * 1e3;
|
|
1654
|
+
}
|
|
1655
|
+
async function loadContextConfig(cwd) {
|
|
1656
|
+
const configPath = path10.join(cwd, ".cursor", "context", "config.json");
|
|
1657
|
+
return readJson(configPath);
|
|
1658
|
+
}
|
|
1659
|
+
async function stampCursorUpdateCheck(cwd, patch) {
|
|
1660
|
+
const configPath = path10.join(cwd, ".cursor", "context", "config.json");
|
|
1661
|
+
const existing = await loadContextConfig(cwd) ?? {};
|
|
1662
|
+
const prev = existing.cursorUpdateCheck && typeof existing.cursorUpdateCheck === "object" ? { ...existing.cursorUpdateCheck } : {};
|
|
1663
|
+
const nextSeen = patch.lastSeenCursorVersion !== void 0 ? patch.lastSeenCursorVersion : void 0;
|
|
1664
|
+
if (nextSeen !== void 0 && nextSeen !== null && !isPlausibleCursorVersion(nextSeen)) {
|
|
1665
|
+
existing.cursorUpdateCheck = {
|
|
1666
|
+
...DEFAULT_PREFS,
|
|
1667
|
+
...prev,
|
|
1668
|
+
lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1669
|
+
};
|
|
1670
|
+
await writeJson(configPath, existing);
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
existing.cursorUpdateCheck = {
|
|
1674
|
+
...DEFAULT_PREFS,
|
|
1675
|
+
...prev,
|
|
1676
|
+
lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1677
|
+
...nextSeen !== void 0 ? { lastSeenCursorVersion: nextSeen } : {}
|
|
1678
|
+
};
|
|
1679
|
+
await writeJson(configPath, existing);
|
|
1680
|
+
}
|
|
1681
|
+
function isPlausibleCursorVersion(version) {
|
|
1682
|
+
const parts = String(version).split(".");
|
|
1683
|
+
if (parts.length < 2 || parts.length > 3) return false;
|
|
1684
|
+
const major = Number(parts[0]);
|
|
1685
|
+
const minor = Number(parts[1]);
|
|
1686
|
+
const patch = parts.length === 3 ? Number(parts[2]) : 0;
|
|
1687
|
+
if ([major, minor, patch].some((n) => Number.isNaN(n) || n < 0)) return false;
|
|
1688
|
+
if (major >= 2e3 || major > CURSOR_VERSION_MAJOR_MAX) return false;
|
|
1689
|
+
if (minor > 999 || patch > 999) return false;
|
|
1690
|
+
return true;
|
|
1691
|
+
}
|
|
1692
|
+
function extractLatestCursorVersion(changelogText) {
|
|
1693
|
+
const text = String(changelogText ?? "");
|
|
1694
|
+
const candidates = [];
|
|
1695
|
+
const push = (major, minor, patch) => {
|
|
1696
|
+
const version = patch !== void 0 ? `${major}.${minor}.${patch}` : `${major}.${minor}`;
|
|
1697
|
+
if (isPlausibleCursorVersion(version)) candidates.push(version);
|
|
1698
|
+
};
|
|
1699
|
+
for (const m of text.matchAll(/<span class="label">(\d+)\.(\d+)(?:\.(\d+))?<\/span>/gi)) {
|
|
1700
|
+
push(m[1] ?? "", m[2] ?? "", m[3]);
|
|
1701
|
+
}
|
|
1702
|
+
for (const m of text.matchAll(
|
|
1703
|
+
/\b(\d+)\.(\d+)(?:\.(\d+))?\b(?=[^\n]{0,48}(?:Changelog|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))/gi
|
|
1704
|
+
)) {
|
|
1705
|
+
push(m[1] ?? "", m[2] ?? "", m[3]);
|
|
1706
|
+
}
|
|
1707
|
+
if (candidates.length === 0) {
|
|
1708
|
+
for (const m of text.matchAll(/\b(\d+)\.(\d+)(?:\.(\d+))?\b/g)) {
|
|
1709
|
+
push(m[1] ?? "", m[2] ?? "", m[3]);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
let best = null;
|
|
1713
|
+
let bestScore = -1;
|
|
1714
|
+
for (const version of candidates) {
|
|
1715
|
+
const parts = version.split(".").map(Number);
|
|
1716
|
+
const score = (parts[0] ?? 0) * 1e6 + (parts[1] ?? 0) * 1e3 + (parts[2] ?? 0);
|
|
1717
|
+
if (score > bestScore) {
|
|
1718
|
+
bestScore = score;
|
|
1719
|
+
best = version;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
return best;
|
|
1723
|
+
}
|
|
1724
|
+
function compareCursorVersion(a, b) {
|
|
1725
|
+
const pa = a.split(".").map((p) => Number(p));
|
|
1726
|
+
const pb = b.split(".").map((p) => Number(p));
|
|
1727
|
+
const len = Math.max(pa.length, pb.length);
|
|
1728
|
+
for (let i = 0; i < len; i++) {
|
|
1729
|
+
const da = pa[i] ?? 0;
|
|
1730
|
+
const db = pb[i] ?? 0;
|
|
1731
|
+
if (Number.isNaN(da) || Number.isNaN(db)) return 0;
|
|
1732
|
+
if (da !== db) return da < db ? -1 : 1;
|
|
1733
|
+
}
|
|
1734
|
+
return 0;
|
|
1735
|
+
}
|
|
1736
|
+
function parseInventoryRefreshed(markdown) {
|
|
1737
|
+
const m = markdown.match(/last refreshed\s+\*\*(\d{4}-\d{2}-\d{2})\*\*/i);
|
|
1738
|
+
return m?.[1] ?? null;
|
|
1739
|
+
}
|
|
1740
|
+
function parseOpenActionIds(markdown) {
|
|
1741
|
+
const ids = [];
|
|
1742
|
+
for (const line of markdown.split("\n")) {
|
|
1743
|
+
const m = line.match(/^\|\s*([A-Z]\d+)\s*\|\s*Open\s*\|/i);
|
|
1744
|
+
if (m?.[1]) ids.push(m[1]);
|
|
1745
|
+
}
|
|
1746
|
+
return ids;
|
|
1747
|
+
}
|
|
1748
|
+
function parseFeatureMapNames(markdown) {
|
|
1749
|
+
const names = [];
|
|
1750
|
+
let inTable = false;
|
|
1751
|
+
for (const line of markdown.split("\n")) {
|
|
1752
|
+
if (line.startsWith("| Feature |")) {
|
|
1753
|
+
inTable = true;
|
|
1754
|
+
continue;
|
|
1755
|
+
}
|
|
1756
|
+
if (inTable) {
|
|
1757
|
+
if (!line.startsWith("|")) break;
|
|
1758
|
+
if (line.includes("---")) continue;
|
|
1759
|
+
const cells = line.split("|").map((c) => c.trim());
|
|
1760
|
+
const name = cells[1];
|
|
1761
|
+
if (name && name.toLowerCase() !== "feature") names.push(name);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return names;
|
|
1765
|
+
}
|
|
1766
|
+
async function defaultFetchText(url) {
|
|
1767
|
+
if (!url.startsWith("https://")) {
|
|
1768
|
+
throw new Error(`Refusing non-HTTPS changelog URL: ${url}`);
|
|
1769
|
+
}
|
|
1770
|
+
const res = await fetch(url, {
|
|
1771
|
+
headers: { Accept: "text/html,text/plain;q=0.9,*/*;q=0.8" },
|
|
1772
|
+
redirect: "follow",
|
|
1773
|
+
signal: AbortSignal.timeout(CHANGELOG_FETCH_TIMEOUT_MS)
|
|
1774
|
+
});
|
|
1775
|
+
if (!res.ok) {
|
|
1776
|
+
throw new Error(`Changelog fetch failed: HTTP ${res.status}`);
|
|
1777
|
+
}
|
|
1778
|
+
return res.text();
|
|
1779
|
+
}
|
|
1780
|
+
function baseResult(partial) {
|
|
1781
|
+
return {
|
|
1782
|
+
...partial,
|
|
1783
|
+
applyRecommended: false,
|
|
1784
|
+
fieldReportRecommended: false,
|
|
1785
|
+
conveyorHint: CONVEYOR_HINT
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
async function checkCursorUpdateAwareness(cwd, options = {}) {
|
|
1789
|
+
const inventoryPath = path10.join(cwd, INVENTORY_REL);
|
|
1790
|
+
const featuresPath = path10.join(cwd, FEATURES_REL);
|
|
1791
|
+
const prefs = readCursorUpdateCheckPrefs(await loadContextConfig(cwd));
|
|
1792
|
+
const changelogUrl = options.changelogUrl ?? prefs.changelogUrl;
|
|
1793
|
+
if (options.respectPrefs) {
|
|
1794
|
+
if (!prefs.enabled) {
|
|
1795
|
+
return baseResult({
|
|
1796
|
+
status: "skipped-disabled",
|
|
1797
|
+
inventoryPath: INVENTORY_REL,
|
|
1798
|
+
featuresPath: FEATURES_REL,
|
|
1799
|
+
changelogUrl,
|
|
1800
|
+
latestCursorVersion: null,
|
|
1801
|
+
lastSeenCursorVersion: prefs.lastSeenCursorVersion,
|
|
1802
|
+
inventoryRefreshed: null,
|
|
1803
|
+
openActionIds: [],
|
|
1804
|
+
gaps: [],
|
|
1805
|
+
message: "cursorUpdateCheck.enabled is false (opt-in). Set true in .cursor/context/config.json to nudge."
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
if (!intervalElapsed(prefs.lastCheckedAt, prefs.intervalDays)) {
|
|
1809
|
+
return baseResult({
|
|
1810
|
+
status: "skipped-interval",
|
|
1811
|
+
inventoryPath: INVENTORY_REL,
|
|
1812
|
+
featuresPath: FEATURES_REL,
|
|
1813
|
+
changelogUrl,
|
|
1814
|
+
latestCursorVersion: null,
|
|
1815
|
+
lastSeenCursorVersion: prefs.lastSeenCursorVersion,
|
|
1816
|
+
inventoryRefreshed: null,
|
|
1817
|
+
openActionIds: [],
|
|
1818
|
+
gaps: [],
|
|
1819
|
+
message: `Within cursorUpdateCheck.intervalDays (${prefs.intervalDays}); last check ${prefs.lastCheckedAt}.`
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
let inventoryMd;
|
|
1824
|
+
try {
|
|
1825
|
+
inventoryMd = await readFile6(inventoryPath, "utf8");
|
|
1826
|
+
} catch {
|
|
1827
|
+
return baseResult({
|
|
1828
|
+
status: "error",
|
|
1829
|
+
inventoryPath: INVENTORY_REL,
|
|
1830
|
+
featuresPath: FEATURES_REL,
|
|
1831
|
+
changelogUrl,
|
|
1832
|
+
latestCursorVersion: null,
|
|
1833
|
+
lastSeenCursorVersion: prefs.lastSeenCursorVersion,
|
|
1834
|
+
inventoryRefreshed: null,
|
|
1835
|
+
openActionIds: [],
|
|
1836
|
+
gaps: [],
|
|
1837
|
+
message: `Missing inventory at ${INVENTORY_REL}.`
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
const inventoryRefreshed = parseInventoryRefreshed(inventoryMd);
|
|
1841
|
+
const openActionIds = parseOpenActionIds(inventoryMd);
|
|
1842
|
+
const gaps = [];
|
|
1843
|
+
for (const id of openActionIds) {
|
|
1844
|
+
gaps.push({
|
|
1845
|
+
id: `open-action-${id}`,
|
|
1846
|
+
severity: "advisory",
|
|
1847
|
+
path: INVENTORY_REL,
|
|
1848
|
+
evidence: `Action item ${id} is Open in the native-audit inventory`,
|
|
1849
|
+
suggestedRoute: "backlog-add"
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
if (inventoryRefreshed) {
|
|
1853
|
+
const refreshedMs = Date.parse(inventoryRefreshed);
|
|
1854
|
+
if (!Number.isNaN(refreshedMs)) {
|
|
1855
|
+
const ageDays = (Date.now() - refreshedMs) / (24 * 60 * 60 * 1e3);
|
|
1856
|
+
if (ageDays > 45) {
|
|
1857
|
+
gaps.push({
|
|
1858
|
+
id: "inventory-stale",
|
|
1859
|
+
severity: "stale",
|
|
1860
|
+
path: INVENTORY_REL,
|
|
1861
|
+
evidence: `Inventory last refreshed ${inventoryRefreshed} (>45 days). Version-prose refresh owned by parked Marketplace plan; awareness reports only.`,
|
|
1862
|
+
suggestedRoute: "none"
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
try {
|
|
1868
|
+
const featuresMd = await readFile6(featuresPath, "utf8");
|
|
1869
|
+
if (parseFeatureMapNames(featuresMd).length === 0) {
|
|
1870
|
+
gaps.push({
|
|
1871
|
+
id: "features-map-empty",
|
|
1872
|
+
severity: "info",
|
|
1873
|
+
path: FEATURES_REL,
|
|
1874
|
+
evidence: "Secondary feature map has no Feature table rows",
|
|
1875
|
+
suggestedRoute: "none"
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
} catch {
|
|
1879
|
+
gaps.push({
|
|
1880
|
+
id: "features-map-missing",
|
|
1881
|
+
severity: "info",
|
|
1882
|
+
path: FEATURES_REL,
|
|
1883
|
+
evidence: "Secondary feature map missing; changelog/inventory check continues",
|
|
1884
|
+
suggestedRoute: "none"
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
let latestCursorVersion = null;
|
|
1888
|
+
if (!options.offline) {
|
|
1889
|
+
try {
|
|
1890
|
+
const body = options.changelogBody ?? await (options.fetchText ?? defaultFetchText)(changelogUrl);
|
|
1891
|
+
latestCursorVersion = extractLatestCursorVersion(body);
|
|
1892
|
+
if (latestCursorVersion && prefs.lastSeenCursorVersion && compareCursorVersion(latestCursorVersion, prefs.lastSeenCursorVersion) > 0) {
|
|
1893
|
+
gaps.push({
|
|
1894
|
+
id: "changelog-ahead",
|
|
1895
|
+
severity: "advisory",
|
|
1896
|
+
path: changelogUrl,
|
|
1897
|
+
evidence: `Changelog latest ${latestCursorVersion} is ahead of lastSeenCursorVersion ${prefs.lastSeenCursorVersion}`,
|
|
1898
|
+
suggestedRoute: "backlog-add"
|
|
1899
|
+
});
|
|
1900
|
+
} else if (latestCursorVersion && !prefs.lastSeenCursorVersion) {
|
|
1901
|
+
gaps.push({
|
|
1902
|
+
id: "changelog-baseline",
|
|
1903
|
+
severity: "info",
|
|
1904
|
+
path: changelogUrl,
|
|
1905
|
+
evidence: `Changelog latest ${latestCursorVersion}; no lastSeenCursorVersion baseline yet (stamp to baseline)`,
|
|
1906
|
+
suggestedRoute: "none"
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1911
|
+
return baseResult({
|
|
1912
|
+
status: "error",
|
|
1913
|
+
inventoryPath: INVENTORY_REL,
|
|
1914
|
+
featuresPath: FEATURES_REL,
|
|
1915
|
+
changelogUrl,
|
|
1916
|
+
latestCursorVersion: null,
|
|
1917
|
+
lastSeenCursorVersion: prefs.lastSeenCursorVersion,
|
|
1918
|
+
inventoryRefreshed,
|
|
1919
|
+
openActionIds,
|
|
1920
|
+
gaps,
|
|
1921
|
+
message: `Changelog fetch error: ${msg}`
|
|
1922
|
+
});
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
if (options.stamp) {
|
|
1926
|
+
await stampCursorUpdateCheck(cwd, {
|
|
1927
|
+
lastSeenCursorVersion: latestCursorVersion ?? prefs.lastSeenCursorVersion
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
const status = gaps.length > 0 ? "gaps-found" : "current";
|
|
1931
|
+
const message = status === "current" ? "No advisory Cursor-update gaps vs inventory (check-only)." : `Found ${gaps.length} advisory gap(s). ${CONVEYOR_HINT}`;
|
|
1932
|
+
return baseResult({
|
|
1933
|
+
status,
|
|
1934
|
+
inventoryPath: INVENTORY_REL,
|
|
1935
|
+
featuresPath: FEATURES_REL,
|
|
1936
|
+
changelogUrl: options.offline ? null : changelogUrl,
|
|
1937
|
+
latestCursorVersion,
|
|
1938
|
+
lastSeenCursorVersion: prefs.lastSeenCursorVersion,
|
|
1939
|
+
inventoryRefreshed,
|
|
1940
|
+
openActionIds,
|
|
1941
|
+
gaps,
|
|
1942
|
+
message
|
|
1943
|
+
});
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// src/commands/cursor-awareness.ts
|
|
1947
|
+
var cursorAwarenessCommand = defineCommand3({
|
|
1948
|
+
meta: {
|
|
1949
|
+
name: "cursor-awareness",
|
|
1950
|
+
description: "Opt-in advisory: diff Cursor changelog / native-audit inventory for gaps (never apply, never Field Reports)"
|
|
1951
|
+
},
|
|
1952
|
+
args: {
|
|
1953
|
+
cwd: {
|
|
1954
|
+
type: "string",
|
|
1955
|
+
default: process.cwd()
|
|
1956
|
+
},
|
|
1957
|
+
check: {
|
|
1958
|
+
type: "boolean",
|
|
1959
|
+
description: "Check-only (default true; apply is never supported)",
|
|
1960
|
+
default: true
|
|
1961
|
+
},
|
|
1962
|
+
json: {
|
|
1963
|
+
type: "boolean",
|
|
1964
|
+
description: "Print machine-readable JSON",
|
|
1965
|
+
default: false
|
|
1966
|
+
},
|
|
1967
|
+
"respect-prefs": {
|
|
1968
|
+
type: "boolean",
|
|
1969
|
+
description: "Honor cursorUpdateCheck.enabled and intervalDays from .cursor/context/config.json",
|
|
1970
|
+
default: false
|
|
1971
|
+
},
|
|
1972
|
+
stamp: {
|
|
1973
|
+
type: "boolean",
|
|
1974
|
+
description: "Persist cursorUpdateCheck.lastCheckedAt / lastSeenCursorVersion",
|
|
1975
|
+
default: false
|
|
1976
|
+
},
|
|
1977
|
+
offline: {
|
|
1978
|
+
type: "boolean",
|
|
1979
|
+
description: "Skip changelog network fetch; inventory-only advisory",
|
|
1980
|
+
default: false
|
|
1981
|
+
}
|
|
1982
|
+
},
|
|
1983
|
+
async run({ args }) {
|
|
1984
|
+
void args.check;
|
|
1985
|
+
const result = await checkCursorUpdateAwareness(args.cwd, {
|
|
1986
|
+
respectPrefs: Boolean(args["respect-prefs"]),
|
|
1987
|
+
stamp: Boolean(args.stamp),
|
|
1988
|
+
offline: Boolean(args.offline)
|
|
1989
|
+
});
|
|
1990
|
+
if (args.json) {
|
|
1991
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1992
|
+
} else {
|
|
1993
|
+
const line = `[${result.status}] ${result.message}`;
|
|
1994
|
+
if (result.status === "error") logger.error(line);
|
|
1995
|
+
else if (result.status === "gaps-found") logger.warn(line);
|
|
1996
|
+
else if (result.status.startsWith("skipped-")) logger.warn(line);
|
|
1997
|
+
else logger.info(line);
|
|
1998
|
+
if (result.gaps.length > 0 && !args.json) {
|
|
1999
|
+
for (const gap of result.gaps.slice(0, 12)) {
|
|
2000
|
+
logger.info(`- ${gap.id} (${gap.severity}): ${gap.evidence} \u2192 ${gap.suggestedRoute}`);
|
|
2001
|
+
}
|
|
2002
|
+
if (result.gaps.length > 12) {
|
|
2003
|
+
logger.info(`\u2026 and ${result.gaps.length - 12} more (use --json)`);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
if (result.status === "error") process.exitCode = 2;
|
|
2008
|
+
}
|
|
2009
|
+
});
|
|
2010
|
+
|
|
1409
2011
|
// src/commands/dashboard-broadcast.ts
|
|
1410
2012
|
import { spawn as spawn2 } from "child_process";
|
|
1411
2013
|
import { access as access3 } from "fs/promises";
|
|
1412
|
-
import
|
|
1413
|
-
import { defineCommand as
|
|
2014
|
+
import path12 from "path";
|
|
2015
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
1414
2016
|
|
|
1415
2017
|
// src/commands/dashboard.ts
|
|
1416
2018
|
import { execFileSync, spawn } from "child_process";
|
|
1417
2019
|
import { access as access2 } from "fs/promises";
|
|
1418
|
-
import
|
|
2020
|
+
import path11 from "path";
|
|
1419
2021
|
import { fileURLToPath } from "url";
|
|
1420
|
-
import { defineCommand as
|
|
2022
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
1421
2023
|
function bundledDashboardCandidates(filename, moduleUrl = import.meta.url) {
|
|
1422
|
-
const here =
|
|
2024
|
+
const here = path11.dirname(fileURLToPath(moduleUrl));
|
|
1423
2025
|
return [
|
|
1424
2026
|
// Published / built: <pkg>/dist/index.js -> <pkg>/dashboard/
|
|
1425
|
-
|
|
2027
|
+
path11.join(here, "..", "dashboard", filename),
|
|
1426
2028
|
// Dev source: packages/cli/src/commands/*.ts -> packages/cli/dashboard/ (sync copy)
|
|
1427
|
-
|
|
2029
|
+
path11.join(here, "..", "..", "dashboard", filename),
|
|
1428
2030
|
// From src/commands: packages/dashboard/ (unused layout); from dist: two levels above package
|
|
1429
|
-
|
|
2031
|
+
path11.join(here, "..", "..", "..", "dashboard", filename)
|
|
1430
2032
|
];
|
|
1431
2033
|
}
|
|
1432
2034
|
async function firstExisting(candidates) {
|
|
1433
2035
|
for (const candidate2 of candidates) {
|
|
1434
2036
|
try {
|
|
1435
2037
|
await access2(candidate2);
|
|
1436
|
-
return
|
|
2038
|
+
return path11.resolve(candidate2);
|
|
1437
2039
|
} catch {
|
|
1438
2040
|
}
|
|
1439
2041
|
}
|
|
1440
2042
|
return null;
|
|
1441
2043
|
}
|
|
1442
2044
|
async function findDashboardStart(cwd, env = process.env, options = {}) {
|
|
1443
|
-
let dir =
|
|
2045
|
+
let dir = path11.resolve(cwd);
|
|
1444
2046
|
for (; ; ) {
|
|
1445
|
-
const candidate2 =
|
|
2047
|
+
const candidate2 = path11.join(dir, "dashboard", "start.mjs");
|
|
1446
2048
|
try {
|
|
1447
2049
|
await access2(candidate2);
|
|
1448
2050
|
return candidate2;
|
|
1449
2051
|
} catch {
|
|
1450
2052
|
}
|
|
1451
|
-
const parent =
|
|
2053
|
+
const parent = path11.dirname(dir);
|
|
1452
2054
|
if (parent === dir) break;
|
|
1453
2055
|
dir = parent;
|
|
1454
2056
|
}
|
|
1455
2057
|
for (const key of ["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]) {
|
|
1456
2058
|
const base = env[key];
|
|
1457
2059
|
if (!base || typeof base !== "string" || !base.trim()) continue;
|
|
1458
|
-
const candidate2 =
|
|
2060
|
+
const candidate2 = path11.join(path11.resolve(base.trim()), "dashboard", "start.mjs");
|
|
1459
2061
|
try {
|
|
1460
2062
|
await access2(candidate2);
|
|
1461
2063
|
return candidate2;
|
|
1462
2064
|
} catch {
|
|
1463
2065
|
}
|
|
1464
2066
|
}
|
|
1465
|
-
const sibling =
|
|
2067
|
+
const sibling = path11.join(path11.resolve(cwd), "..", "agent-kit", "dashboard", "start.mjs");
|
|
1466
2068
|
try {
|
|
1467
2069
|
await access2(sibling);
|
|
1468
|
-
return
|
|
2070
|
+
return path11.resolve(sibling);
|
|
1469
2071
|
} catch {
|
|
1470
2072
|
}
|
|
1471
2073
|
return firstExisting(
|
|
@@ -1473,13 +2075,13 @@ async function findDashboardStart(cwd, env = process.env, options = {}) {
|
|
|
1473
2075
|
);
|
|
1474
2076
|
}
|
|
1475
2077
|
function resolveDashboardSnapshotRoot(cwd) {
|
|
1476
|
-
const abs =
|
|
2078
|
+
const abs = path11.resolve(cwd);
|
|
1477
2079
|
try {
|
|
1478
2080
|
const top = execFileSync("git", ["-C", abs, "rev-parse", "--show-toplevel"], {
|
|
1479
2081
|
encoding: "utf8",
|
|
1480
2082
|
timeout: 5e3
|
|
1481
2083
|
}).trim();
|
|
1482
|
-
if (top) return
|
|
2084
|
+
if (top) return path11.resolve(top);
|
|
1483
2085
|
} catch {
|
|
1484
2086
|
}
|
|
1485
2087
|
return abs;
|
|
@@ -1487,7 +2089,7 @@ function resolveDashboardSnapshotRoot(cwd) {
|
|
|
1487
2089
|
function runStartScript(startPath, env) {
|
|
1488
2090
|
return new Promise((resolve, reject) => {
|
|
1489
2091
|
const child = spawn(process.execPath, [startPath], {
|
|
1490
|
-
cwd:
|
|
2092
|
+
cwd: path11.dirname(path11.dirname(startPath)),
|
|
1491
2093
|
env,
|
|
1492
2094
|
stdio: "inherit"
|
|
1493
2095
|
});
|
|
@@ -1495,7 +2097,7 @@ function runStartScript(startPath, env) {
|
|
|
1495
2097
|
child.on("close", (code) => resolve(code ?? 1));
|
|
1496
2098
|
});
|
|
1497
2099
|
}
|
|
1498
|
-
var dashboardCommand =
|
|
2100
|
+
var dashboardCommand = defineCommand4({
|
|
1499
2101
|
meta: {
|
|
1500
2102
|
name: "dashboard",
|
|
1501
2103
|
description: "Start Mission Control for this workspace (stable per-root port) and open the panel URL."
|
|
@@ -1532,30 +2134,30 @@ var dashboardCommand = defineCommand3({
|
|
|
1532
2134
|
|
|
1533
2135
|
// src/commands/dashboard-broadcast.ts
|
|
1534
2136
|
async function findDashboardBroadcastStart(cwd, env = process.env, options = {}) {
|
|
1535
|
-
let dir =
|
|
2137
|
+
let dir = path12.resolve(cwd);
|
|
1536
2138
|
for (; ; ) {
|
|
1537
|
-
const candidate2 =
|
|
2139
|
+
const candidate2 = path12.join(dir, "dashboard", "start-broadcast.mjs");
|
|
1538
2140
|
try {
|
|
1539
2141
|
await access3(candidate2);
|
|
1540
2142
|
return candidate2;
|
|
1541
2143
|
} catch {
|
|
1542
2144
|
}
|
|
1543
|
-
const parent =
|
|
2145
|
+
const parent = path12.dirname(dir);
|
|
1544
2146
|
if (parent === dir) break;
|
|
1545
2147
|
dir = parent;
|
|
1546
2148
|
}
|
|
1547
2149
|
for (const key of ["MISSION_CONTROL_KIT_ROOT", "AGENT_KIT_HOME"]) {
|
|
1548
2150
|
const base = env[key];
|
|
1549
2151
|
if (!base || typeof base !== "string" || !base.trim()) continue;
|
|
1550
|
-
const candidate2 =
|
|
2152
|
+
const candidate2 = path12.join(path12.resolve(base.trim()), "dashboard", "start-broadcast.mjs");
|
|
1551
2153
|
try {
|
|
1552
2154
|
await access3(candidate2);
|
|
1553
2155
|
return candidate2;
|
|
1554
2156
|
} catch {
|
|
1555
2157
|
}
|
|
1556
2158
|
}
|
|
1557
|
-
const sibling =
|
|
1558
|
-
|
|
2159
|
+
const sibling = path12.join(
|
|
2160
|
+
path12.resolve(cwd),
|
|
1559
2161
|
"..",
|
|
1560
2162
|
"agent-kit",
|
|
1561
2163
|
"dashboard",
|
|
@@ -1563,7 +2165,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
1563
2165
|
);
|
|
1564
2166
|
try {
|
|
1565
2167
|
await access3(sibling);
|
|
1566
|
-
return
|
|
2168
|
+
return path12.resolve(sibling);
|
|
1567
2169
|
} catch {
|
|
1568
2170
|
}
|
|
1569
2171
|
for (const candidate2 of bundledDashboardCandidates(
|
|
@@ -1572,7 +2174,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
1572
2174
|
)) {
|
|
1573
2175
|
try {
|
|
1574
2176
|
await access3(candidate2);
|
|
1575
|
-
return
|
|
2177
|
+
return path12.resolve(candidate2);
|
|
1576
2178
|
} catch {
|
|
1577
2179
|
}
|
|
1578
2180
|
}
|
|
@@ -1581,7 +2183,7 @@ async function findDashboardBroadcastStart(cwd, env = process.env, options = {})
|
|
|
1581
2183
|
function runStartScript2(startPath, env) {
|
|
1582
2184
|
return new Promise((resolve, reject) => {
|
|
1583
2185
|
const child = spawn2(process.execPath, [startPath], {
|
|
1584
|
-
cwd:
|
|
2186
|
+
cwd: path12.dirname(path12.dirname(startPath)),
|
|
1585
2187
|
env,
|
|
1586
2188
|
stdio: "inherit"
|
|
1587
2189
|
});
|
|
@@ -1589,7 +2191,7 @@ function runStartScript2(startPath, env) {
|
|
|
1589
2191
|
child.on("close", (code) => resolve(code ?? 1));
|
|
1590
2192
|
});
|
|
1591
2193
|
}
|
|
1592
|
-
var dashboardBroadcastCommand =
|
|
2194
|
+
var dashboardBroadcastCommand = defineCommand5({
|
|
1593
2195
|
meta: {
|
|
1594
2196
|
name: "dashboard-broadcast",
|
|
1595
2197
|
description: "Opt-in LAN Mission Control (token-gated). Terminal counterpart to /dashboard-broadcast. Does not change loopback /dashboard."
|
|
@@ -1624,20 +2226,20 @@ var dashboardBroadcastCommand = defineCommand4({
|
|
|
1624
2226
|
});
|
|
1625
2227
|
|
|
1626
2228
|
// src/commands/diff.ts
|
|
1627
|
-
import { defineCommand as
|
|
2229
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
1628
2230
|
|
|
1629
2231
|
// src/lifecycle/diff.ts
|
|
1630
|
-
import { readFile as
|
|
1631
|
-
import
|
|
2232
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
2233
|
+
import path13 from "path";
|
|
1632
2234
|
async function readIfExists(abs) {
|
|
1633
2235
|
try {
|
|
1634
|
-
return await
|
|
2236
|
+
return await readFile7(abs, "utf8");
|
|
1635
2237
|
} catch {
|
|
1636
2238
|
return null;
|
|
1637
2239
|
}
|
|
1638
2240
|
}
|
|
1639
2241
|
async function comparePair(registryRoot, projectRoot, sourceRel, targetRel, protectedGlobs) {
|
|
1640
|
-
const posixTarget = targetRel.split(
|
|
2242
|
+
const posixTarget = targetRel.split(path13.sep).join("/");
|
|
1641
2243
|
if (isProtectedPath(posixTarget, protectedGlobs)) {
|
|
1642
2244
|
return { path: posixTarget, status: "protected" };
|
|
1643
2245
|
}
|
|
@@ -1667,7 +2269,7 @@ async function diffAgainstRegistry(registryRoot, projectRoot, manifest) {
|
|
|
1667
2269
|
const entries = [];
|
|
1668
2270
|
const seen = /* @__PURE__ */ new Set();
|
|
1669
2271
|
const pushUnique = async (sourceRel, targetRel) => {
|
|
1670
|
-
const key = targetRel.split(
|
|
2272
|
+
const key = targetRel.split(path13.sep).join("/");
|
|
1671
2273
|
if (seen.has(key)) return;
|
|
1672
2274
|
seen.add(key);
|
|
1673
2275
|
entries.push(
|
|
@@ -1694,8 +2296,8 @@ async function diffAgainstRegistry(registryRoot, projectRoot, manifest) {
|
|
|
1694
2296
|
continue;
|
|
1695
2297
|
}
|
|
1696
2298
|
const category = skill.path.includes("/core/") ? "core" : "community";
|
|
1697
|
-
const sourceRel =
|
|
1698
|
-
const targetRel =
|
|
2299
|
+
const sourceRel = path13.posix.join(skill.path, "SKILL.md");
|
|
2300
|
+
const targetRel = path13.posix.join(".cursor", "skills", category, skill.id, "SKILL.md");
|
|
1699
2301
|
await pushUnique(sourceRel, targetRel);
|
|
1700
2302
|
}
|
|
1701
2303
|
}
|
|
@@ -1714,7 +2316,7 @@ function summarizeDiff(entries) {
|
|
|
1714
2316
|
}
|
|
1715
2317
|
|
|
1716
2318
|
// src/commands/diff.ts
|
|
1717
|
-
var diffCommand =
|
|
2319
|
+
var diffCommand = defineCommand6({
|
|
1718
2320
|
meta: {
|
|
1719
2321
|
name: "diff",
|
|
1720
2322
|
description: "Compare installed kit artifacts to the registry (respects L3 protected)."
|
|
@@ -1763,15 +2365,16 @@ var diffCommand = defineCommand5({
|
|
|
1763
2365
|
});
|
|
1764
2366
|
|
|
1765
2367
|
// src/commands/doctor.ts
|
|
1766
|
-
import
|
|
1767
|
-
import { defineCommand as
|
|
2368
|
+
import path24 from "path";
|
|
2369
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
1768
2370
|
|
|
1769
2371
|
// src/invariants/hooks-health.ts
|
|
1770
2372
|
import { execFile as execFile2 } from "child_process";
|
|
1771
|
-
import { constants as constants2, access as access4, readFile as
|
|
1772
|
-
import
|
|
2373
|
+
import { constants as constants2, access as access4, readFile as readFile8, readdir as readdir3, stat } from "fs/promises";
|
|
2374
|
+
import path14 from "path";
|
|
1773
2375
|
import { promisify as promisify2 } from "util";
|
|
1774
2376
|
var execFileAsync2 = promisify2(execFile2);
|
|
2377
|
+
var GIT_HOOK_CANONICAL_NAMES = ["pre-commit", "pre-push", "prepare-commit-msg"];
|
|
1775
2378
|
var EXPECTED_EVENTS = [
|
|
1776
2379
|
"sessionStart",
|
|
1777
2380
|
"preCompact",
|
|
@@ -1796,10 +2399,10 @@ async function isExecutable(p) {
|
|
|
1796
2399
|
}
|
|
1797
2400
|
}
|
|
1798
2401
|
async function resolveAgentKitCli(rootDir) {
|
|
1799
|
-
const root =
|
|
2402
|
+
const root = path14.resolve(rootDir);
|
|
1800
2403
|
const candidates = [
|
|
1801
|
-
|
|
1802
|
-
|
|
2404
|
+
path14.join(root, "node_modules", ".bin", "agent-kit"),
|
|
2405
|
+
path14.join(root, "packages", "cli", "dist", "index.js")
|
|
1803
2406
|
];
|
|
1804
2407
|
for (const c of candidates) {
|
|
1805
2408
|
if (await exists(c)) return c;
|
|
@@ -1818,16 +2421,83 @@ function commandLooksLikeAdapter(command) {
|
|
|
1818
2421
|
const m = trimmed.match(/(\.cursor\/hooks\/agent\/[A-Za-z0-9._-]+\.sh)\b/);
|
|
1819
2422
|
return m?.[1] ?? null;
|
|
1820
2423
|
}
|
|
2424
|
+
async function assessGitHooksInstallDrift(rootDir) {
|
|
2425
|
+
const root = path14.resolve(rootDir);
|
|
2426
|
+
const canonicalDir = path14.join(root, "git-hooks");
|
|
2427
|
+
if (!await exists(canonicalDir)) return [];
|
|
2428
|
+
let hooksPathCfg = "";
|
|
2429
|
+
try {
|
|
2430
|
+
const { stdout } = await execFileAsync2(
|
|
2431
|
+
"git",
|
|
2432
|
+
["-C", root, "config", "--get", "core.hooksPath"],
|
|
2433
|
+
{ encoding: "utf8" }
|
|
2434
|
+
);
|
|
2435
|
+
hooksPathCfg = stdout.trim();
|
|
2436
|
+
} catch {
|
|
2437
|
+
}
|
|
2438
|
+
if (hooksPathCfg) {
|
|
2439
|
+
const resolved = path14.isAbsolute(hooksPathCfg) ? path14.normalize(hooksPathCfg) : path14.normalize(path14.join(root, hooksPathCfg));
|
|
2440
|
+
if (resolved === path14.normalize(canonicalDir)) {
|
|
2441
|
+
return [];
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
const gitDir = path14.join(root, ".git");
|
|
2445
|
+
if (!await exists(gitDir)) return [];
|
|
2446
|
+
try {
|
|
2447
|
+
const st = await stat(gitDir);
|
|
2448
|
+
if (!st.isDirectory()) return [];
|
|
2449
|
+
} catch {
|
|
2450
|
+
return [];
|
|
2451
|
+
}
|
|
2452
|
+
const installedDir = path14.join(gitDir, "hooks");
|
|
2453
|
+
const advisories = [];
|
|
2454
|
+
const installHint = "Install or refresh with: `cp git-hooks/<name> .git/hooks/<name> && chmod +x .git/hooks/<name>` (see git-hooks/README.md)";
|
|
2455
|
+
let names = [...GIT_HOOK_CANONICAL_NAMES];
|
|
2456
|
+
try {
|
|
2457
|
+
const listed = await readdir3(canonicalDir);
|
|
2458
|
+
const fromDisk = listed.filter(
|
|
2459
|
+
(n) => GIT_HOOK_CANONICAL_NAMES.includes(n)
|
|
2460
|
+
);
|
|
2461
|
+
if (fromDisk.length > 0) names = fromDisk;
|
|
2462
|
+
} catch {
|
|
2463
|
+
}
|
|
2464
|
+
for (const name of names) {
|
|
2465
|
+
const canonical = path14.join(canonicalDir, name);
|
|
2466
|
+
const installed = path14.join(installedDir, name);
|
|
2467
|
+
if (!await exists(canonical)) continue;
|
|
2468
|
+
if (!await exists(installed)) {
|
|
2469
|
+
advisories.push(
|
|
2470
|
+
`git-hooks drift: \`.git/hooks/${name}\` missing (canonical \`git-hooks/${name}\` present). ${installHint}`
|
|
2471
|
+
);
|
|
2472
|
+
continue;
|
|
2473
|
+
}
|
|
2474
|
+
try {
|
|
2475
|
+
const [a, b] = await Promise.all([readFile8(canonical, "utf8"), readFile8(installed, "utf8")]);
|
|
2476
|
+
if (a !== b) {
|
|
2477
|
+
advisories.push(
|
|
2478
|
+
`git-hooks drift: \`.git/hooks/${name}\` differs from \`git-hooks/${name}\`. ${installHint}`
|
|
2479
|
+
);
|
|
2480
|
+
}
|
|
2481
|
+
} catch {
|
|
2482
|
+
advisories.push(
|
|
2483
|
+
`git-hooks drift: could not compare \`git-hooks/${name}\` with \`.git/hooks/${name}\`. ${installHint}`
|
|
2484
|
+
);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
return advisories;
|
|
2488
|
+
}
|
|
1821
2489
|
async function assessHooksHealth(rootDir) {
|
|
1822
|
-
const root =
|
|
2490
|
+
const root = path14.resolve(rootDir);
|
|
1823
2491
|
const hooksJsonPath = ".cursor/hooks.json";
|
|
1824
|
-
const hooksJsonAbs =
|
|
2492
|
+
const hooksJsonAbs = path14.join(root, hooksJsonPath);
|
|
1825
2493
|
const reasons = [];
|
|
1826
2494
|
const wiredEvents = [];
|
|
2495
|
+
const advisories = await assessGitHooksInstallDrift(root);
|
|
1827
2496
|
if (!await exists(hooksJsonAbs)) {
|
|
1828
2497
|
return {
|
|
1829
2498
|
status: "missing",
|
|
1830
2499
|
reasons: ["`.cursor/hooks.json` not found"],
|
|
2500
|
+
advisories,
|
|
1831
2501
|
hooksJsonPath,
|
|
1832
2502
|
expectedEvents: [...EXPECTED_EVENTS],
|
|
1833
2503
|
wiredEvents
|
|
@@ -1835,11 +2505,12 @@ async function assessHooksHealth(rootDir) {
|
|
|
1835
2505
|
}
|
|
1836
2506
|
let parsed;
|
|
1837
2507
|
try {
|
|
1838
|
-
parsed = JSON.parse(await
|
|
2508
|
+
parsed = JSON.parse(await readFile8(hooksJsonAbs, "utf8"));
|
|
1839
2509
|
} catch {
|
|
1840
2510
|
return {
|
|
1841
2511
|
status: "degraded",
|
|
1842
2512
|
reasons: ["`.cursor/hooks.json` is not valid JSON"],
|
|
2513
|
+
advisories,
|
|
1843
2514
|
hooksJsonPath,
|
|
1844
2515
|
expectedEvents: [...EXPECTED_EVENTS],
|
|
1845
2516
|
wiredEvents
|
|
@@ -1864,14 +2535,14 @@ async function assessHooksHealth(rootDir) {
|
|
|
1864
2535
|
reasons.push(`missing hook event: ${event}`);
|
|
1865
2536
|
}
|
|
1866
2537
|
}
|
|
1867
|
-
const resolveLib =
|
|
2538
|
+
const resolveLib = path14.join(root, ".cursor", "hooks", "agent", "resolve-agent-kit.sh");
|
|
1868
2539
|
if (!await exists(resolveLib)) {
|
|
1869
2540
|
reasons.push("missing `.cursor/hooks/agent/resolve-agent-kit.sh` (thin adapter resolver)");
|
|
1870
2541
|
} else if (!await isExecutable(resolveLib)) {
|
|
1871
2542
|
reasons.push("`.cursor/hooks/agent/resolve-agent-kit.sh` is not executable (chmod +x)");
|
|
1872
2543
|
}
|
|
1873
2544
|
for (const rel of adapterRels) {
|
|
1874
|
-
const abs =
|
|
2545
|
+
const abs = path14.join(root, rel);
|
|
1875
2546
|
if (!await exists(abs)) {
|
|
1876
2547
|
reasons.push(`missing adapter script: \`${rel}\``);
|
|
1877
2548
|
continue;
|
|
@@ -1903,6 +2574,7 @@ async function assessHooksHealth(rootDir) {
|
|
|
1903
2574
|
return {
|
|
1904
2575
|
status,
|
|
1905
2576
|
reasons,
|
|
2577
|
+
advisories,
|
|
1906
2578
|
hooksJsonPath,
|
|
1907
2579
|
expectedEvents: [...EXPECTED_EVENTS],
|
|
1908
2580
|
wiredEvents
|
|
@@ -1910,7 +2582,7 @@ async function assessHooksHealth(rootDir) {
|
|
|
1910
2582
|
}
|
|
1911
2583
|
|
|
1912
2584
|
// src/scanner/readiness.ts
|
|
1913
|
-
import { createHash as
|
|
2585
|
+
import { createHash as createHash3 } from "crypto";
|
|
1914
2586
|
function action(id, status, recommendation, owner) {
|
|
1915
2587
|
return { id, status, recommendation, owner };
|
|
1916
2588
|
}
|
|
@@ -1938,7 +2610,7 @@ function repositoryFingerprint(scan) {
|
|
|
1938
2610
|
context: scan.context.sources,
|
|
1939
2611
|
agentKitVersion: scan.agentKit.version
|
|
1940
2612
|
};
|
|
1941
|
-
return
|
|
2613
|
+
return createHash3("sha256").update(JSON.stringify(fingerprintInput)).digest("hex");
|
|
1942
2614
|
}
|
|
1943
2615
|
function buildPillars(scan) {
|
|
1944
2616
|
const agentKitStatus = scan.agentKit.installed ? "ready" : "auto_fix";
|
|
@@ -2156,12 +2828,12 @@ function createReadinessReport(scan, options) {
|
|
|
2156
2828
|
}
|
|
2157
2829
|
|
|
2158
2830
|
// src/scanner/safe-fixes.ts
|
|
2159
|
-
import { readFile as
|
|
2160
|
-
import
|
|
2831
|
+
import { readFile as readFile11, writeFile as writeFile4 } from "fs/promises";
|
|
2832
|
+
import path22 from "path";
|
|
2161
2833
|
|
|
2162
2834
|
// src/scanner/detect-repository.ts
|
|
2163
|
-
import { readFile as
|
|
2164
|
-
import
|
|
2835
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2836
|
+
import path15 from "path";
|
|
2165
2837
|
var CONTEXT_PATHS = [
|
|
2166
2838
|
["README.md", "README"],
|
|
2167
2839
|
["README", "README"],
|
|
@@ -2180,7 +2852,7 @@ var CONTEXT_PATHS = [
|
|
|
2180
2852
|
async function existingEvidence(rootDir, candidates) {
|
|
2181
2853
|
const evidence = await Promise.all(
|
|
2182
2854
|
candidates.map(
|
|
2183
|
-
async ([relativePath, label]) => await fileExists(
|
|
2855
|
+
async ([relativePath, label]) => await fileExists(path15.join(rootDir, relativePath)) ? { source: "file", value: `${relativePath}:${label}` } : void 0
|
|
2184
2856
|
)
|
|
2185
2857
|
);
|
|
2186
2858
|
return evidence.flatMap((item) => item ? [item] : []);
|
|
@@ -2201,7 +2873,7 @@ async function detectContext(rootDir) {
|
|
|
2201
2873
|
async function detectPurpose(rootDir, stack) {
|
|
2202
2874
|
const entries = await listDirectory(rootDir);
|
|
2203
2875
|
const lowerEntries = entries.map((entry) => entry.toLowerCase());
|
|
2204
|
-
const packageJson = await readJson(
|
|
2876
|
+
const packageJson = await readJson(path15.join(rootDir, "package.json"));
|
|
2205
2877
|
const categories = [];
|
|
2206
2878
|
const evidence = [];
|
|
2207
2879
|
const add = (category, value2) => {
|
|
@@ -2241,16 +2913,16 @@ async function detectPurpose(rootDir, stack) {
|
|
|
2241
2913
|
}
|
|
2242
2914
|
async function detectAgentKit(rootDir) {
|
|
2243
2915
|
const manifestRelativePath = ".cursor/agent-kit.json";
|
|
2244
|
-
const manifestPath =
|
|
2916
|
+
const manifestPath = path15.join(rootDir, manifestRelativePath);
|
|
2245
2917
|
const installed = await fileExists(manifestPath);
|
|
2246
2918
|
const manifest = installed ? await readJson(manifestPath) : null;
|
|
2247
2919
|
return {
|
|
2248
2920
|
installed,
|
|
2249
2921
|
manifestPath: installed ? manifestRelativePath : void 0,
|
|
2250
2922
|
version: manifest?.version,
|
|
2251
|
-
hasPlans: await fileExists(
|
|
2252
|
-
hasHandoff: await fileExists(
|
|
2253
|
-
hasMemory: await fileExists(
|
|
2923
|
+
hasPlans: await fileExists(path15.join(rootDir, ".cursor/plans")),
|
|
2924
|
+
hasHandoff: await fileExists(path15.join(rootDir, ".cursor/HANDOFF.md")),
|
|
2925
|
+
hasMemory: await fileExists(path15.join(rootDir, ".cursor/memory"))
|
|
2254
2926
|
};
|
|
2255
2927
|
}
|
|
2256
2928
|
var REQUIRED_SECRET_PATTERNS = [
|
|
@@ -2264,9 +2936,9 @@ var REQUIRED_SECRET_PATTERNS = [
|
|
|
2264
2936
|
"*service-account*.json"
|
|
2265
2937
|
];
|
|
2266
2938
|
async function detectSafety(rootDir, trackedFiles) {
|
|
2267
|
-
const gitignorePath =
|
|
2939
|
+
const gitignorePath = path15.join(rootDir, ".gitignore");
|
|
2268
2940
|
const hasGitignore = await fileExists(gitignorePath);
|
|
2269
|
-
const gitignore = hasGitignore ? await
|
|
2941
|
+
const gitignore = hasGitignore ? await readFile9(gitignorePath, "utf8") : "";
|
|
2270
2942
|
const lines = gitignore.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
2271
2943
|
const ignoredSecretPatterns = REQUIRED_SECRET_PATTERNS.filter(
|
|
2272
2944
|
(pattern) => lines.includes(pattern)
|
|
@@ -2275,7 +2947,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
2275
2947
|
(file) => /(^|\/)(\.env(\..+)?|.*\.(key|pem|p12|pfx)|.*credentials.*\.json)$/i.test(file)
|
|
2276
2948
|
);
|
|
2277
2949
|
const hookPaths = [".husky", ".git/hooks/pre-commit", "git-hooks/pre-commit"];
|
|
2278
|
-
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(
|
|
2950
|
+
const hasHooks = (await Promise.all(hookPaths.map((item) => fileExists(path15.join(rootDir, item))))).some(Boolean);
|
|
2279
2951
|
const guardCandidates = [
|
|
2280
2952
|
".husky/pre-commit",
|
|
2281
2953
|
".husky/pre-push",
|
|
@@ -2284,7 +2956,7 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
2284
2956
|
];
|
|
2285
2957
|
const guardContents = await Promise.all(
|
|
2286
2958
|
guardCandidates.map(
|
|
2287
|
-
async (item) => await fileExists(
|
|
2959
|
+
async (item) => await fileExists(path15.join(rootDir, item)) ? readFile9(path15.join(rootDir, item), "utf8") : ""
|
|
2288
2960
|
)
|
|
2289
2961
|
);
|
|
2290
2962
|
return {
|
|
@@ -2302,11 +2974,11 @@ async function detectSafety(rootDir, trackedFiles) {
|
|
|
2302
2974
|
}
|
|
2303
2975
|
|
|
2304
2976
|
// src/scanner/scan.ts
|
|
2305
|
-
import
|
|
2977
|
+
import path21 from "path";
|
|
2306
2978
|
|
|
2307
2979
|
// src/scanner/detect-git.ts
|
|
2308
2980
|
import { execFile as execFile3 } from "child_process";
|
|
2309
|
-
import
|
|
2981
|
+
import path16 from "path";
|
|
2310
2982
|
import { promisify as promisify3 } from "util";
|
|
2311
2983
|
var exec = promisify3(execFile3);
|
|
2312
2984
|
function remoteHostname(remoteUrl) {
|
|
@@ -2330,7 +3002,7 @@ function sanitizeRemoteUrl(remoteUrl) {
|
|
|
2330
3002
|
}
|
|
2331
3003
|
async function detectProvider(rootDir, remoteUrl) {
|
|
2332
3004
|
const configuration = await readJson(
|
|
2333
|
-
|
|
3005
|
+
path16.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
2334
3006
|
);
|
|
2335
3007
|
const configuredProvider = configuration?.git?.provider;
|
|
2336
3008
|
if (configuredProvider) {
|
|
@@ -2387,7 +3059,7 @@ async function detectProvider(rootDir, remoteUrl) {
|
|
|
2387
3059
|
evidence: remoteEvidence
|
|
2388
3060
|
};
|
|
2389
3061
|
}
|
|
2390
|
-
if (await fileExists(
|
|
3062
|
+
if (await fileExists(path16.join(rootDir, ".gitlab-ci.yml"))) {
|
|
2391
3063
|
return {
|
|
2392
3064
|
provider: "gitlab",
|
|
2393
3065
|
providerKind: "gitlab-self-hosted",
|
|
@@ -2476,11 +3148,11 @@ async function detectGit(rootDir) {
|
|
|
2476
3148
|
}
|
|
2477
3149
|
|
|
2478
3150
|
// src/scanner/detect-ide.ts
|
|
2479
|
-
import
|
|
3151
|
+
import path17 from "path";
|
|
2480
3152
|
async function detectIde(rootDir) {
|
|
2481
|
-
const hasCursor = await fileExists(
|
|
2482
|
-
const hasVSCode = await fileExists(
|
|
2483
|
-
const hasWindsurf = await fileExists(
|
|
3153
|
+
const hasCursor = await fileExists(path17.join(rootDir, ".cursor"));
|
|
3154
|
+
const hasVSCode = await fileExists(path17.join(rootDir, ".vscode"));
|
|
3155
|
+
const hasWindsurf = await fileExists(path17.join(rootDir, ".windsurfrules"));
|
|
2484
3156
|
if (hasCursor) return { ide: "cursor", plan: "cursor-pro" };
|
|
2485
3157
|
if (hasVSCode) return { ide: "vscode", plan: "vscode-pro" };
|
|
2486
3158
|
if (hasWindsurf) return { ide: "windsurf", plan: "windsurf" };
|
|
@@ -2488,7 +3160,7 @@ async function detectIde(rootDir) {
|
|
|
2488
3160
|
}
|
|
2489
3161
|
|
|
2490
3162
|
// src/scanner/detect-infra.ts
|
|
2491
|
-
import
|
|
3163
|
+
import path18 from "path";
|
|
2492
3164
|
|
|
2493
3165
|
// src/types.ts
|
|
2494
3166
|
var CI_PLATFORM_FILES = {
|
|
@@ -2517,12 +3189,12 @@ var PM_TOOL_LABELS = {
|
|
|
2517
3189
|
|
|
2518
3190
|
// src/scanner/detect-infra.ts
|
|
2519
3191
|
async function detectInfra(rootDir) {
|
|
2520
|
-
const docker = await fileExists(
|
|
2521
|
-
const kubernetes = await fileExists(
|
|
3192
|
+
const docker = await fileExists(path18.join(rootDir, "Dockerfile")) || await fileExists(path18.join(rootDir, "docker-compose.yml")) || await fileExists(path18.join(rootDir, "docker-compose.yaml"));
|
|
3193
|
+
const kubernetes = await fileExists(path18.join(rootDir, "k8s")) || await fileExists(path18.join(rootDir, "kubernetes"));
|
|
2522
3194
|
let ci = "none";
|
|
2523
3195
|
const ciFiles = [];
|
|
2524
3196
|
for (const [platform, filePath] of Object.entries(CI_PLATFORM_FILES)) {
|
|
2525
|
-
if (await fileExists(
|
|
3197
|
+
if (await fileExists(path18.join(rootDir, filePath))) {
|
|
2526
3198
|
if (ci === "none") ci = platform;
|
|
2527
3199
|
ciFiles.push(filePath);
|
|
2528
3200
|
}
|
|
@@ -2547,30 +3219,30 @@ async function detectInfra(rootDir) {
|
|
|
2547
3219
|
];
|
|
2548
3220
|
const infrastructureFiles = (await Promise.all(
|
|
2549
3221
|
infrastructureCandidates.map(
|
|
2550
|
-
async (file) => await fileExists(
|
|
3222
|
+
async (file) => await fileExists(path18.join(rootDir, file)) ? file : void 0
|
|
2551
3223
|
)
|
|
2552
3224
|
)).filter((file) => file !== void 0);
|
|
2553
3225
|
const deploymentFiles = (await Promise.all(
|
|
2554
3226
|
deploymentCandidates.map(
|
|
2555
|
-
async (file) => await fileExists(
|
|
3227
|
+
async (file) => await fileExists(path18.join(rootDir, file)) ? file : void 0
|
|
2556
3228
|
)
|
|
2557
3229
|
)).filter((file) => file !== void 0);
|
|
2558
3230
|
return { docker, kubernetes, ci, ciFiles, infrastructureFiles, deploymentFiles };
|
|
2559
3231
|
}
|
|
2560
3232
|
|
|
2561
3233
|
// src/scanner/detect-services.ts
|
|
2562
|
-
import { readFile as
|
|
2563
|
-
import
|
|
3234
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
3235
|
+
import path19 from "path";
|
|
2564
3236
|
async function detectProjectManagement(rootDir) {
|
|
2565
3237
|
const tools = [];
|
|
2566
3238
|
const mcpConfigPaths = [
|
|
2567
|
-
|
|
2568
|
-
|
|
3239
|
+
path19.join(rootDir, ".cursor", "mcp.json"),
|
|
3240
|
+
path19.join(rootDir, "mcp.json")
|
|
2569
3241
|
];
|
|
2570
3242
|
for (const configPath of mcpConfigPaths) {
|
|
2571
3243
|
if (!await fileExists(configPath)) continue;
|
|
2572
3244
|
try {
|
|
2573
|
-
const raw = await
|
|
3245
|
+
const raw = await readFile10(configPath, "utf8");
|
|
2574
3246
|
const lower = raw.toLowerCase();
|
|
2575
3247
|
if (lower.includes("clickup")) tools.push("clickup");
|
|
2576
3248
|
if (lower.includes("jira") || lower.includes("atlassian")) tools.push("jira");
|
|
@@ -2581,20 +3253,20 @@ async function detectProjectManagement(rootDir) {
|
|
|
2581
3253
|
} catch {
|
|
2582
3254
|
}
|
|
2583
3255
|
}
|
|
2584
|
-
if (await fileExists(
|
|
3256
|
+
if (await fileExists(path19.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
|
|
2585
3257
|
tools.push("github-issues");
|
|
2586
3258
|
}
|
|
2587
|
-
if (await fileExists(
|
|
3259
|
+
if (await fileExists(path19.join(rootDir, ".github", "projects"))) {
|
|
2588
3260
|
tools.push("github-projects");
|
|
2589
3261
|
}
|
|
2590
3262
|
return [...new Set(tools)];
|
|
2591
3263
|
}
|
|
2592
3264
|
async function detectServices(rootDir) {
|
|
2593
|
-
const hasPrisma = await fileExists(
|
|
2594
|
-
const hasSequelize = await fileExists(
|
|
2595
|
-
const hasDrizzle = await fileExists(
|
|
2596
|
-
const hasKnex = await fileExists(
|
|
2597
|
-
const hasTypeorm = await fileExists(
|
|
3265
|
+
const hasPrisma = await fileExists(path19.join(rootDir, "prisma/schema.prisma"));
|
|
3266
|
+
const hasSequelize = await fileExists(path19.join(rootDir, "sequelize"));
|
|
3267
|
+
const hasDrizzle = await fileExists(path19.join(rootDir, "drizzle.config.ts"));
|
|
3268
|
+
const hasKnex = await fileExists(path19.join(rootDir, "knexfile.ts"));
|
|
3269
|
+
const hasTypeorm = await fileExists(path19.join(rootDir, "ormconfig.json"));
|
|
2598
3270
|
const database = hasPrisma || hasSequelize || hasDrizzle || hasKnex || hasTypeorm ? "postgresql" : void 0;
|
|
2599
3271
|
const orm = hasPrisma ? "prisma" : hasDrizzle ? "drizzle" : hasSequelize ? "sequelize" : hasKnex ? "knex" : hasTypeorm ? "typeorm" : void 0;
|
|
2600
3272
|
const projectManagement = await detectProjectManagement(rootDir);
|
|
@@ -2606,7 +3278,7 @@ async function detectServices(rootDir) {
|
|
|
2606
3278
|
}
|
|
2607
3279
|
|
|
2608
3280
|
// src/scanner/detect-stack.ts
|
|
2609
|
-
import
|
|
3281
|
+
import path20 from "path";
|
|
2610
3282
|
var PROJECT_MARKERS = [
|
|
2611
3283
|
"package.json",
|
|
2612
3284
|
"requirements.txt",
|
|
@@ -2635,7 +3307,7 @@ async function detectPackageManager(rootDir, packageJson) {
|
|
|
2635
3307
|
};
|
|
2636
3308
|
}
|
|
2637
3309
|
for (const [lockfile, packageManager] of LOCKFILES) {
|
|
2638
|
-
if (await fileExists(
|
|
3310
|
+
if (await fileExists(path20.join(rootDir, lockfile))) {
|
|
2639
3311
|
return {
|
|
2640
3312
|
packageManager,
|
|
2641
3313
|
evidence: [{ source: "file", value: lockfile }]
|
|
@@ -2652,27 +3324,27 @@ function commandsForScripts(scripts, packageManager) {
|
|
|
2652
3324
|
return { testCommands, validationCommands };
|
|
2653
3325
|
}
|
|
2654
3326
|
async function detectStack(rootDir) {
|
|
2655
|
-
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(
|
|
2656
|
-
const hasPackageJson = await fileExists(
|
|
3327
|
+
const hasAnyProjectMarker = (await Promise.all(PROJECT_MARKERS.map((item) => fileExists(path20.join(rootDir, item))))).some(Boolean);
|
|
3328
|
+
const hasPackageJson = await fileExists(path20.join(rootDir, "package.json"));
|
|
2657
3329
|
if (hasPackageJson) {
|
|
2658
|
-
const packageJson = await readJson(
|
|
3330
|
+
const packageJson = await readJson(path20.join(rootDir, "package.json")) ?? {};
|
|
2659
3331
|
const scripts = packageJson.scripts ?? {};
|
|
2660
3332
|
const packageManager = await detectPackageManager(rootDir, packageJson);
|
|
2661
3333
|
const commands = commandsForScripts(scripts, packageManager.packageManager);
|
|
2662
|
-
const hasNextConfig = await fileExists(
|
|
2663
|
-
const hasNestConfig = await fileExists(
|
|
3334
|
+
const hasNextConfig = await fileExists(path20.join(rootDir, "next.config.js")) || await fileExists(path20.join(rootDir, "next.config.mjs")) || await fileExists(path20.join(rootDir, "next.config.ts"));
|
|
3335
|
+
const hasNestConfig = await fileExists(path20.join(rootDir, "nest-cli.json"));
|
|
2664
3336
|
return {
|
|
2665
3337
|
language: "node",
|
|
2666
3338
|
framework: hasNextConfig ? "nextjs" : hasNestConfig ? "nestjs" : "node",
|
|
2667
3339
|
packageManager: packageManager.packageManager,
|
|
2668
3340
|
packageManagerEvidence: packageManager.evidence,
|
|
2669
3341
|
scripts,
|
|
2670
|
-
workspaces: packageJson.workspaces !== void 0 || await fileExists(
|
|
3342
|
+
workspaces: packageJson.workspaces !== void 0 || await fileExists(path20.join(rootDir, "pnpm-workspace.yaml")),
|
|
2671
3343
|
...commands,
|
|
2672
3344
|
hasProjectFiles: hasAnyProjectMarker
|
|
2673
3345
|
};
|
|
2674
3346
|
}
|
|
2675
|
-
if (await fileExists(
|
|
3347
|
+
if (await fileExists(path20.join(rootDir, "pyproject.toml"))) {
|
|
2676
3348
|
return {
|
|
2677
3349
|
language: "python",
|
|
2678
3350
|
framework: "python",
|
|
@@ -2682,7 +3354,7 @@ async function detectStack(rootDir) {
|
|
|
2682
3354
|
hasProjectFiles: hasAnyProjectMarker
|
|
2683
3355
|
};
|
|
2684
3356
|
}
|
|
2685
|
-
if (await fileExists(
|
|
3357
|
+
if (await fileExists(path20.join(rootDir, "go.mod"))) {
|
|
2686
3358
|
return {
|
|
2687
3359
|
language: "go",
|
|
2688
3360
|
framework: "go",
|
|
@@ -2692,7 +3364,7 @@ async function detectStack(rootDir) {
|
|
|
2692
3364
|
hasProjectFiles: hasAnyProjectMarker
|
|
2693
3365
|
};
|
|
2694
3366
|
}
|
|
2695
|
-
if (await fileExists(
|
|
3367
|
+
if (await fileExists(path20.join(rootDir, "Cargo.toml"))) {
|
|
2696
3368
|
return {
|
|
2697
3369
|
language: "rust",
|
|
2698
3370
|
framework: "rust",
|
|
@@ -2702,7 +3374,7 @@ async function detectStack(rootDir) {
|
|
|
2702
3374
|
hasProjectFiles: hasAnyProjectMarker
|
|
2703
3375
|
};
|
|
2704
3376
|
}
|
|
2705
|
-
if (await fileExists(
|
|
3377
|
+
if (await fileExists(path20.join(rootDir, "composer.json"))) {
|
|
2706
3378
|
return {
|
|
2707
3379
|
language: "php",
|
|
2708
3380
|
framework: "php",
|
|
@@ -2735,7 +3407,7 @@ function isGreenfieldByEntries(entries) {
|
|
|
2735
3407
|
return meaningful.length === 0;
|
|
2736
3408
|
}
|
|
2737
3409
|
async function runScanner(rootDir) {
|
|
2738
|
-
const normalizedRoot =
|
|
3410
|
+
const normalizedRoot = path21.resolve(rootDir);
|
|
2739
3411
|
const entries = await listDirectory(normalizedRoot);
|
|
2740
3412
|
const stack = await detectStack(normalizedRoot);
|
|
2741
3413
|
const purpose = await detectPurpose(normalizedRoot, stack);
|
|
@@ -2939,7 +3611,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2939
3611
|
});
|
|
2940
3612
|
const changes = [];
|
|
2941
3613
|
for (const relativePath of ESSENTIAL_DIRECTORIES) {
|
|
2942
|
-
const absolutePath =
|
|
3614
|
+
const absolutePath = path22.join(beforeScan.rootDir, relativePath);
|
|
2943
3615
|
const exists2 = await fileExists(absolutePath);
|
|
2944
3616
|
if (!exists2 && !dryRun) await ensureDir(absolutePath);
|
|
2945
3617
|
recordChange(
|
|
@@ -2952,11 +3624,11 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2952
3624
|
);
|
|
2953
3625
|
}
|
|
2954
3626
|
const gitignoreRelativePath = ".gitignore";
|
|
2955
|
-
const gitignorePath =
|
|
2956
|
-
const existingGitignore = await fileExists(gitignorePath) ? await
|
|
3627
|
+
const gitignorePath = path22.join(beforeScan.rootDir, gitignoreRelativePath);
|
|
3628
|
+
const existingGitignore = await fileExists(gitignorePath) ? await readFile11(gitignorePath, "utf8") : "";
|
|
2957
3629
|
const mergedGitignore = mergeSecretIgnores(existingGitignore);
|
|
2958
3630
|
const gitignoreChanged = mergedGitignore !== existingGitignore;
|
|
2959
|
-
if (gitignoreChanged && !dryRun) await
|
|
3631
|
+
if (gitignoreChanged && !dryRun) await writeFile4(gitignorePath, mergedGitignore, "utf8");
|
|
2960
3632
|
recordChange(
|
|
2961
3633
|
changes,
|
|
2962
3634
|
"merge-secret-ignores",
|
|
@@ -2968,7 +3640,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2968
3640
|
gitignoreChanged ? "required secret patterns are missing" : "required patterns are present"
|
|
2969
3641
|
)
|
|
2970
3642
|
);
|
|
2971
|
-
const profilePath =
|
|
3643
|
+
const profilePath = path22.join(beforeScan.rootDir, PROFILE_RELATIVE_PATH);
|
|
2972
3644
|
const existingProfile = await readJson(profilePath) ?? {};
|
|
2973
3645
|
const desiredProfile = createProfile(beforeScan, before, generatedAt);
|
|
2974
3646
|
const mergedProfile = mergeMissing(existingProfile, desiredProfile);
|
|
@@ -2990,7 +3662,7 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
2990
3662
|
generatorVersion: options.generatorVersion,
|
|
2991
3663
|
generatedAt
|
|
2992
3664
|
});
|
|
2993
|
-
const contextConfigPath =
|
|
3665
|
+
const contextConfigPath = path22.join(beforeScan.rootDir, CONTEXT_CONFIG_RELATIVE_PATH);
|
|
2994
3666
|
const existingContextConfig = await readJson(contextConfigPath) ?? {};
|
|
2995
3667
|
const onboarding = reconcileOnboardingState(evidenceReport, existingContextConfig, generatedAt);
|
|
2996
3668
|
const defaults = preferenceDefaults(onboarding, existingContextConfig.onboarded);
|
|
@@ -3019,17 +3691,17 @@ async function executeSafeReadinessFixes(rootDir, options) {
|
|
|
3019
3691
|
}
|
|
3020
3692
|
|
|
3021
3693
|
// src/scanner/snapshot.ts
|
|
3022
|
-
import
|
|
3694
|
+
import path23 from "path";
|
|
3023
3695
|
var READINESS_SNAPSHOT_RELATIVE_PATH = ".cursor/context/readiness.json";
|
|
3024
3696
|
async function writeReadinessSnapshot(rootDir, report) {
|
|
3025
|
-
const snapshotPath =
|
|
3697
|
+
const snapshotPath = path23.join(rootDir, READINESS_SNAPSHOT_RELATIVE_PATH);
|
|
3026
3698
|
await writeJson(snapshotPath, report);
|
|
3027
3699
|
return snapshotPath;
|
|
3028
3700
|
}
|
|
3029
3701
|
|
|
3030
3702
|
// src/commands/doctor.ts
|
|
3031
3703
|
async function runDoctor(cwd, options = {}) {
|
|
3032
|
-
const rootDir =
|
|
3704
|
+
const rootDir = path24.resolve(cwd);
|
|
3033
3705
|
const hooks = await assessHooksHealth(rootDir);
|
|
3034
3706
|
if (options.fixSafe) {
|
|
3035
3707
|
const execution = await executeSafeReadinessFixes(rootDir, {
|
|
@@ -3063,11 +3735,22 @@ function printDoctorSummary(result) {
|
|
|
3063
3735
|
console.log(` - ${reason}`);
|
|
3064
3736
|
}
|
|
3065
3737
|
}
|
|
3738
|
+
if (result.hooks.advisories.length > 0) {
|
|
3739
|
+
console.log("hooks advisories (soft; install via cp, see git-hooks/README.md):");
|
|
3740
|
+
for (const tip of result.hooks.advisories.slice(0, 5)) {
|
|
3741
|
+
console.log(` - ${tip}`);
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
if (process.env.ALLOW_MAIN_PUSH === "1") {
|
|
3745
|
+
console.log("\u26A0\uFE0F WARNING: ALLOW_MAIN_PUSH=1 is set in environment");
|
|
3746
|
+
console.log(" This disables main-push protection for agent Shell commands.");
|
|
3747
|
+
console.log(" Consider unsetting it: unset ALLOW_MAIN_PUSH");
|
|
3748
|
+
}
|
|
3066
3749
|
console.log(
|
|
3067
3750
|
nextAction ? `Next: ${nextAction.recommendation}` : "Next: repository readiness checks are complete"
|
|
3068
3751
|
);
|
|
3069
3752
|
}
|
|
3070
|
-
var doctorCommand =
|
|
3753
|
+
var doctorCommand = defineCommand7({
|
|
3071
3754
|
meta: {
|
|
3072
3755
|
name: "doctor",
|
|
3073
3756
|
description: "Diagnose repository readiness and optionally apply safe local fixes."
|
|
@@ -3101,7 +3784,7 @@ var doctorCommand = defineCommand6({
|
|
|
3101
3784
|
// src/commands/guard.ts
|
|
3102
3785
|
import { execFile as execFile4 } from "child_process";
|
|
3103
3786
|
import { promisify as promisify4 } from "util";
|
|
3104
|
-
import { defineCommand as
|
|
3787
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
3105
3788
|
|
|
3106
3789
|
// src/hooks/read-stdin-json.ts
|
|
3107
3790
|
async function readStdinJson() {
|
|
@@ -3187,10 +3870,53 @@ var PROTECTED_BRANCH_RE = /^(?:main|master|prod)$/;
|
|
|
3187
3870
|
function normalizeShellCommand(command) {
|
|
3188
3871
|
return command.replace(/\s+/g, " ").trim();
|
|
3189
3872
|
}
|
|
3190
|
-
function
|
|
3873
|
+
function shellSegments(command) {
|
|
3191
3874
|
const normalized = normalizeShellCommand(command);
|
|
3192
3875
|
if (!normalized) return [];
|
|
3193
|
-
return normalized.split(/(?:&&|\|\||[;|])/).map((part) => part.trim()
|
|
3876
|
+
return normalized.split(/(?:&&|\|\||[;|])/).map((part) => part.trim()).filter(Boolean);
|
|
3877
|
+
}
|
|
3878
|
+
function stripLeadingEnvAssignments(segment) {
|
|
3879
|
+
return segment.replace(/^(?:\w+=\S+\s+)*/, "");
|
|
3880
|
+
}
|
|
3881
|
+
function shellInvocationHeads(command) {
|
|
3882
|
+
return shellSegments(command).map(stripLeadingEnvAssignments).filter(Boolean);
|
|
3883
|
+
}
|
|
3884
|
+
function segmentHasAllowMainPushEnv(segment) {
|
|
3885
|
+
if (process.env.ALLOW_MAIN_PUSH === "1") return true;
|
|
3886
|
+
const leading = segment.match(/^(?:\w+=\S+\s+)*/)?.[0] ?? "";
|
|
3887
|
+
return /(?:^|\s)ALLOW_MAIN_PUSH=1(?:\s|$)/.test(leading);
|
|
3888
|
+
}
|
|
3889
|
+
function hasForbiddenMainPushFlags(head) {
|
|
3890
|
+
for (const t of head.split(/\s+/).filter(Boolean)) {
|
|
3891
|
+
if (t === "--force" || t === "-f" || t === "--force-with-lease" || t === "--no-verify" || t === "--all" || t === "--tags") {
|
|
3892
|
+
return true;
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
return false;
|
|
3896
|
+
}
|
|
3897
|
+
function isAuthorizedProdMainPush(head) {
|
|
3898
|
+
if (!/^(?:[\w./-]+\/)?git\s+push\b/.test(head)) return false;
|
|
3899
|
+
if (hasForbiddenMainPushFlags(head)) return false;
|
|
3900
|
+
const after = head.replace(/^(?:[\w./-]+\/)?git\s+push\b/, "").trim();
|
|
3901
|
+
const positional = [];
|
|
3902
|
+
for (const t of after.split(/\s+/).filter(Boolean)) {
|
|
3903
|
+
if (t.startsWith("-")) continue;
|
|
3904
|
+
positional.push(t);
|
|
3905
|
+
}
|
|
3906
|
+
const refspec = positional.length === 2 ? positional[1] : void 0;
|
|
3907
|
+
if (!refspec) return false;
|
|
3908
|
+
const unquoted = refspec.replace(/['"\\]/g, "");
|
|
3909
|
+
if (unquoted.startsWith("+")) return false;
|
|
3910
|
+
if (unquoted.includes(":")) {
|
|
3911
|
+
const src = unquoted.slice(0, unquoted.lastIndexOf(":"));
|
|
3912
|
+
if (src !== "HEAD") return false;
|
|
3913
|
+
}
|
|
3914
|
+
const dest = unquoted.includes(":") ? unquoted.slice(unquoted.lastIndexOf(":") + 1) : unquoted;
|
|
3915
|
+
return normalizePushRefspecToken(dest) === "main";
|
|
3916
|
+
}
|
|
3917
|
+
function segmentAllowsMainPush(segment) {
|
|
3918
|
+
if (!segmentHasAllowMainPushEnv(segment)) return false;
|
|
3919
|
+
return isAuthorizedProdMainPush(stripLeadingEnvAssignments(segment));
|
|
3194
3920
|
}
|
|
3195
3921
|
function anyHeadMatches(command, re) {
|
|
3196
3922
|
return shellInvocationHeads(command).some((head) => re.test(head));
|
|
@@ -3200,9 +3926,7 @@ function isProtectedBranch(name) {
|
|
|
3200
3926
|
}
|
|
3201
3927
|
function normalizePushRefspecToken(token) {
|
|
3202
3928
|
let t = token.trim();
|
|
3203
|
-
|
|
3204
|
-
t = t.slice(1, -1).trim();
|
|
3205
|
-
}
|
|
3929
|
+
t = t.replace(/['"\\]/g, "");
|
|
3206
3930
|
if (t.startsWith("+")) t = t.slice(1);
|
|
3207
3931
|
if (t.startsWith("refs/heads/")) t = t.slice("refs/heads/".length);
|
|
3208
3932
|
if (t.startsWith("origin/")) t = t.slice("origin/".length);
|
|
@@ -3271,7 +3995,9 @@ var SHELL_DENY_RULES = [
|
|
|
3271
3995
|
{
|
|
3272
3996
|
id: "git-push-main",
|
|
3273
3997
|
description: "direct push to main/master/prod bypasses staging",
|
|
3274
|
-
test: (cmd, opts) =>
|
|
3998
|
+
test: (cmd, opts) => shellSegments(cmd).some((segment) => {
|
|
3999
|
+
if (segmentAllowsMainPush(segment)) return false;
|
|
4000
|
+
const head = stripLeadingEnvAssignments(segment);
|
|
3275
4001
|
if (!/^(?:[\w./-]+\/)?git\s+push\b/.test(head)) return false;
|
|
3276
4002
|
if (pushHeadHasProtectedDest(head)) {
|
|
3277
4003
|
return true;
|
|
@@ -3315,13 +4041,13 @@ async function detectCurrentBranch() {
|
|
|
3315
4041
|
return void 0;
|
|
3316
4042
|
}
|
|
3317
4043
|
}
|
|
3318
|
-
var guardCommand =
|
|
4044
|
+
var guardCommand = defineCommand8({
|
|
3319
4045
|
meta: {
|
|
3320
4046
|
name: "guard",
|
|
3321
4047
|
description: "Mechanizable deny/annotate guards (shell, prompt). Hooks are thin adapters."
|
|
3322
4048
|
},
|
|
3323
4049
|
subCommands: {
|
|
3324
|
-
shell:
|
|
4050
|
+
shell: defineCommand8({
|
|
3325
4051
|
meta: {
|
|
3326
4052
|
name: "shell",
|
|
3327
4053
|
description: "Evaluate a shell command against the destructive deny-list"
|
|
@@ -3348,7 +4074,7 @@ var guardCommand = defineCommand7({
|
|
|
3348
4074
|
console.log(JSON.stringify(result));
|
|
3349
4075
|
}
|
|
3350
4076
|
}),
|
|
3351
|
-
prompt:
|
|
4077
|
+
prompt: defineCommand8({
|
|
3352
4078
|
meta: {
|
|
3353
4079
|
name: "prompt",
|
|
3354
4080
|
description: "Scan prompt text for secret patterns (advisory; fail-open at hook)"
|
|
@@ -3382,9 +4108,9 @@ var guardCommand = defineCommand7({
|
|
|
3382
4108
|
|
|
3383
4109
|
// src/commands/handoff.ts
|
|
3384
4110
|
import { spawn as spawn3 } from "child_process";
|
|
3385
|
-
import { readFile as
|
|
3386
|
-
import
|
|
3387
|
-
import { defineCommand as
|
|
4111
|
+
import { readFile as readFile12, readdir as readdir4, writeFile as writeFile5 } from "fs/promises";
|
|
4112
|
+
import path25 from "path";
|
|
4113
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
3388
4114
|
function parsePlanFrontmatter(raw) {
|
|
3389
4115
|
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
3390
4116
|
if (!match?.[1]) return null;
|
|
@@ -3401,21 +4127,21 @@ function parsePlanFrontmatter(raw) {
|
|
|
3401
4127
|
}
|
|
3402
4128
|
async function findActivePlan(plansDir) {
|
|
3403
4129
|
if (!await fileExists(plansDir)) return null;
|
|
3404
|
-
const files = (await
|
|
4130
|
+
const files = (await readdir4(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
|
|
3405
4131
|
for (const file of files) {
|
|
3406
|
-
const raw = await
|
|
4132
|
+
const raw = await readFile12(path25.join(plansDir, file), "utf8");
|
|
3407
4133
|
const fm = parsePlanFrontmatter(raw);
|
|
3408
4134
|
if (fm?.todos?.some((t) => t.status !== "completed" && t.status !== "cancelled")) {
|
|
3409
4135
|
return { file, raw };
|
|
3410
4136
|
}
|
|
3411
4137
|
}
|
|
3412
|
-
return files[0] ? { file: files[0], raw: await
|
|
4138
|
+
return files[0] ? { file: files[0], raw: await readFile12(path25.join(plansDir, files[0]), "utf8") } : null;
|
|
3413
4139
|
}
|
|
3414
4140
|
function now() {
|
|
3415
4141
|
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 16);
|
|
3416
4142
|
}
|
|
3417
4143
|
async function loadProfile(rootDir) {
|
|
3418
|
-
const configPath =
|
|
4144
|
+
const configPath = path25.join(rootDir, ".cursor", "agent-kit.config.json");
|
|
3419
4145
|
try {
|
|
3420
4146
|
return await readJson(configPath);
|
|
3421
4147
|
} catch {
|
|
@@ -3501,7 +4227,7 @@ function runCursorHandoff(scriptPath, cwd) {
|
|
|
3501
4227
|
child.on("close", (code) => resolve(code ?? 1));
|
|
3502
4228
|
});
|
|
3503
4229
|
}
|
|
3504
|
-
var handoffCommand =
|
|
4230
|
+
var handoffCommand = defineCommand9({
|
|
3505
4231
|
meta: {
|
|
3506
4232
|
name: "handoff",
|
|
3507
4233
|
description: "Write .cursor/HANDOFF.md from the active Cursor plan, or run ./cursor-handoff handoff when no plan exists."
|
|
@@ -3515,15 +4241,15 @@ var handoffCommand = defineCommand8({
|
|
|
3515
4241
|
},
|
|
3516
4242
|
async run({ args }) {
|
|
3517
4243
|
const profile = await loadProfile(args.cwd);
|
|
3518
|
-
const plansDir =
|
|
3519
|
-
const handoffPath =
|
|
4244
|
+
const plansDir = path25.join(args.cwd, ".cursor", "plans");
|
|
4245
|
+
const handoffPath = path25.join(args.cwd, ".cursor", "HANDOFF.md");
|
|
3520
4246
|
const plan = await findActivePlan(plansDir);
|
|
3521
4247
|
if (plan) {
|
|
3522
4248
|
const fm = parsePlanFrontmatter(plan.raw);
|
|
3523
4249
|
if (fm) {
|
|
3524
|
-
await ensureDir(
|
|
4250
|
+
await ensureDir(path25.join(args.cwd, ".cursor"));
|
|
3525
4251
|
const content = buildHandoff(plan.file, fm, profile);
|
|
3526
|
-
await
|
|
4252
|
+
await writeFile5(handoffPath, content, "utf8");
|
|
3527
4253
|
logger.success("HANDOFF.md updated: .cursor/HANDOFF.md");
|
|
3528
4254
|
logger.info(
|
|
3529
4255
|
`Plan: ${plan.file} (${fm.todos?.filter((t) => t.status === "completed").length}/${fm.todos?.length} completed)`
|
|
@@ -3541,7 +4267,7 @@ var handoffCommand = defineCommand8({
|
|
|
3541
4267
|
}
|
|
3542
4268
|
logger.warn(`Plan ${plan.file} without valid frontmatter; trying legacy flow.`);
|
|
3543
4269
|
}
|
|
3544
|
-
const scriptPath =
|
|
4270
|
+
const scriptPath = path25.join(args.cwd, "cursor-handoff");
|
|
3545
4271
|
if (!await fileExists(scriptPath)) {
|
|
3546
4272
|
printV3Guidance();
|
|
3547
4273
|
return;
|
|
@@ -3566,8 +4292,8 @@ var handoffCommand = defineCommand8({
|
|
|
3566
4292
|
});
|
|
3567
4293
|
|
|
3568
4294
|
// src/commands/hook.ts
|
|
3569
|
-
import
|
|
3570
|
-
import { defineCommand as
|
|
4295
|
+
import path27 from "path";
|
|
4296
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
3571
4297
|
|
|
3572
4298
|
// src/hooks/pre-compact.ts
|
|
3573
4299
|
function buildPreCompactUserMessage(payload = {}) {
|
|
@@ -3580,8 +4306,8 @@ function buildPreCompactUserMessage(payload = {}) {
|
|
|
3580
4306
|
|
|
3581
4307
|
// src/hooks/session-start.ts
|
|
3582
4308
|
import { spawn as spawn4 } from "child_process";
|
|
3583
|
-
import { access as access5, readFile as
|
|
3584
|
-
import
|
|
4309
|
+
import { access as access5, readFile as readFile13 } from "fs/promises";
|
|
4310
|
+
import path26 from "path";
|
|
3585
4311
|
|
|
3586
4312
|
// src/invariants/handoff-schema.ts
|
|
3587
4313
|
var MACHINE_LIST_CHECKS = [
|
|
@@ -3638,18 +4364,24 @@ var HARD_RULES = `# Agent Kit session hard rules (manual mode default)
|
|
|
3638
4364
|
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).`;
|
|
3639
4365
|
var DOGFOOD_INBOX_HINT = `## Dogfood inbox
|
|
3640
4366
|
|
|
3641
|
-
Unprocessed files are listed under \`dogfood/README.md\` (
|
|
4367
|
+
Unprocessed files are listed under \`dogfood/README.md\` (factory) or \`.cursor/dogfood/README.md\` (consumer). To file a new note, use \`/dogfood <topic> [summary]\`. Follow the ingest ritual (detect \u2192 analyze \u2192 memory WRITE \u2192 triage). Do not auto-start analysis unless the user asks.`;
|
|
3642
4368
|
var UPDATE_CHECK_NUDGE = `## Agent Kit update available
|
|
3643
4369
|
|
|
3644
4370
|
Installed **v{installed}**; latest public **v{latest}**.
|
|
3645
4371
|
|
|
3646
4372
|
This is an advisory only (no files were changed). To apply, run \`/update\` and confirm via Ask questions. Bare \`agent-kit update\` is an explicit operator invoke, not a background job.`;
|
|
4373
|
+
var CURSOR_AWARENESS_NUDGE = `## Cursor product-update awareness
|
|
4374
|
+
|
|
4375
|
+
Advisory gaps vs \`docs/cursor-native-audit.md\` (check-only; no apply; no Field Reports).
|
|
4376
|
+
|
|
4377
|
+
Run \`/cursor-update-awareness\` (or \`agent-kit cursor-awareness --check\`) and confirm routing via Ask \u2192 \`/backlog-add\` or \`/dogfood\`.`;
|
|
3647
4378
|
|
|
3648
4379
|
// src/hooks/session-start.ts
|
|
3649
4380
|
var NONE_PLACEHOLDERS = /* @__PURE__ */ new Set(["none", "n/a", "empty", "nil"]);
|
|
4381
|
+
var CURSOR_AWARENESS_SPAWN_TIMEOUT_MS = CHANGELOG_FETCH_TIMEOUT_MS + 3e3;
|
|
3650
4382
|
async function readTextLimited(filePath, limit = 60) {
|
|
3651
4383
|
try {
|
|
3652
|
-
const lines = (await
|
|
4384
|
+
const lines = (await readFile13(filePath, "utf8")).split(/\r?\n/);
|
|
3653
4385
|
return lines.slice(0, limit).join("\n").trim();
|
|
3654
4386
|
} catch {
|
|
3655
4387
|
return "";
|
|
@@ -3657,7 +4389,7 @@ async function readTextLimited(filePath, limit = 60) {
|
|
|
3657
4389
|
}
|
|
3658
4390
|
async function readFull(filePath) {
|
|
3659
4391
|
try {
|
|
3660
|
-
return await
|
|
4392
|
+
return await readFile13(filePath, "utf8");
|
|
3661
4393
|
} catch {
|
|
3662
4394
|
return "";
|
|
3663
4395
|
}
|
|
@@ -3690,8 +4422,8 @@ function parseUnprocessedDogfoodItems(readmeText) {
|
|
|
3690
4422
|
return items;
|
|
3691
4423
|
}
|
|
3692
4424
|
async function l0Present(root) {
|
|
3693
|
-
const cursor =
|
|
3694
|
-
return await fileExists2(
|
|
4425
|
+
const cursor = path26.join(root, ".cursor");
|
|
4426
|
+
return await fileExists2(path26.join(cursor, "agent-kit.json")) || await fileExists2(path26.join(cursor, "commands", "agent-kit-onboard.md")) || await fileExists2(path26.join(cursor, "commands", "start-project.md"));
|
|
3695
4427
|
}
|
|
3696
4428
|
function checkLabelAndRecommendation(check2) {
|
|
3697
4429
|
const checkId = check2.id;
|
|
@@ -3730,10 +4462,10 @@ function unresolvedReadinessChecks(data) {
|
|
|
3730
4462
|
return { essential, nonessential };
|
|
3731
4463
|
}
|
|
3732
4464
|
async function readinessSection(root) {
|
|
3733
|
-
const snapshotPath =
|
|
4465
|
+
const snapshotPath = path26.join(root, ".cursor", "context", "readiness.json");
|
|
3734
4466
|
let data;
|
|
3735
4467
|
try {
|
|
3736
|
-
data = JSON.parse(await
|
|
4468
|
+
data = JSON.parse(await readFile13(snapshotPath, "utf8"));
|
|
3737
4469
|
} catch {
|
|
3738
4470
|
return null;
|
|
3739
4471
|
}
|
|
@@ -3763,22 +4495,24 @@ Optional readiness item: \`${actionId}\`. ${recommendation} This does not block
|
|
|
3763
4495
|
Optional readiness item: \`${first.id}\`. ${first.recommendation} This does not block \`/start-project\` or active plan work. Resume later with \`/agent-kit-onboard\` if useful.`;
|
|
3764
4496
|
}
|
|
3765
4497
|
async function dogfoodInboxSection(root) {
|
|
3766
|
-
const
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
4498
|
+
const candidateReadmes = [
|
|
4499
|
+
path26.join(root, "dogfood", "README.md"),
|
|
4500
|
+
path26.join(root, ".cursor", "dogfood", "README.md")
|
|
4501
|
+
];
|
|
4502
|
+
for (const readme of candidateReadmes) {
|
|
4503
|
+
if (!await fileExists2(readme)) continue;
|
|
4504
|
+
try {
|
|
4505
|
+
const text = await readFile13(readme, "utf8");
|
|
4506
|
+
if (parseUnprocessedDogfoodItems(text).length) return DOGFOOD_INBOX_HINT;
|
|
4507
|
+
} catch {
|
|
4508
|
+
}
|
|
3776
4509
|
}
|
|
4510
|
+
return null;
|
|
3777
4511
|
}
|
|
3778
4512
|
async function loadUpdateCheckPrefs(root) {
|
|
3779
4513
|
try {
|
|
3780
4514
|
const data = JSON.parse(
|
|
3781
|
-
await
|
|
4515
|
+
await readFile13(path26.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
3782
4516
|
);
|
|
3783
4517
|
const uc = data.updateCheck;
|
|
3784
4518
|
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
@@ -3853,9 +4587,111 @@ async function updateCheckSection(root) {
|
|
|
3853
4587
|
const latest = String(result.latestVersion ?? "?");
|
|
3854
4588
|
return UPDATE_CHECK_NUDGE.replace("{installed}", installed).replace("{latest}", latest);
|
|
3855
4589
|
}
|
|
4590
|
+
async function loadCursorUpdateCheckPrefs(root) {
|
|
4591
|
+
try {
|
|
4592
|
+
const data = JSON.parse(
|
|
4593
|
+
await readFile13(path26.join(root, ".cursor", "context", "config.json"), "utf8")
|
|
4594
|
+
);
|
|
4595
|
+
const uc = data.cursorUpdateCheck;
|
|
4596
|
+
if (!uc || typeof uc !== "object" || uc.enabled !== true) {
|
|
4597
|
+
return null;
|
|
4598
|
+
}
|
|
4599
|
+
return uc;
|
|
4600
|
+
} catch {
|
|
4601
|
+
return null;
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
function runCursorAwarenessJson(root) {
|
|
4605
|
+
return new Promise((resolve) => {
|
|
4606
|
+
const child = spawn4(
|
|
4607
|
+
process.execPath,
|
|
4608
|
+
[
|
|
4609
|
+
process.argv[1] ?? "",
|
|
4610
|
+
"cursor-awareness",
|
|
4611
|
+
"--check",
|
|
4612
|
+
"--json",
|
|
4613
|
+
"--respect-prefs",
|
|
4614
|
+
"--stamp",
|
|
4615
|
+
"--cwd",
|
|
4616
|
+
root
|
|
4617
|
+
],
|
|
4618
|
+
{ stdio: ["ignore", "pipe", "ignore"], timeout: CURSOR_AWARENESS_SPAWN_TIMEOUT_MS }
|
|
4619
|
+
);
|
|
4620
|
+
let out = "";
|
|
4621
|
+
child.stdout?.on("data", (chunk) => {
|
|
4622
|
+
out += chunk.toString("utf8");
|
|
4623
|
+
});
|
|
4624
|
+
child.on("error", () => resolve(null));
|
|
4625
|
+
child.on("close", () => {
|
|
4626
|
+
try {
|
|
4627
|
+
const parsed = JSON.parse(out.trim());
|
|
4628
|
+
resolve(parsed && typeof parsed === "object" ? parsed : null);
|
|
4629
|
+
} catch {
|
|
4630
|
+
resolve(null);
|
|
4631
|
+
}
|
|
4632
|
+
});
|
|
4633
|
+
});
|
|
4634
|
+
}
|
|
4635
|
+
function shouldEmitCursorAwarenessNudge(result) {
|
|
4636
|
+
if (!result || result.status !== "gaps-found") return false;
|
|
4637
|
+
if (result.applyRecommended === true || result.fieldReportRecommended === true) return false;
|
|
4638
|
+
const gaps = Array.isArray(result.gaps) ? result.gaps : [];
|
|
4639
|
+
return gaps.some(
|
|
4640
|
+
(g) => g && typeof g === "object" && g.id === "changelog-ahead"
|
|
4641
|
+
);
|
|
4642
|
+
}
|
|
4643
|
+
async function cursorAwarenessSection(root, deps = {}) {
|
|
4644
|
+
if (await loadCursorUpdateCheckPrefs(root) === null) return null;
|
|
4645
|
+
const spawnFn = deps.spawnFn ?? spawn4;
|
|
4646
|
+
const runFallback = deps.runFallback ?? runCursorAwarenessJson;
|
|
4647
|
+
const result = await new Promise((resolve) => {
|
|
4648
|
+
let settled = false;
|
|
4649
|
+
let fallbackStarted = false;
|
|
4650
|
+
const finish = (value) => {
|
|
4651
|
+
if (settled) return;
|
|
4652
|
+
settled = true;
|
|
4653
|
+
resolve(value);
|
|
4654
|
+
};
|
|
4655
|
+
const startFallback = () => {
|
|
4656
|
+
if (settled || fallbackStarted) return;
|
|
4657
|
+
fallbackStarted = true;
|
|
4658
|
+
void runFallback(root).then(finish);
|
|
4659
|
+
};
|
|
4660
|
+
const child = spawnFn(
|
|
4661
|
+
"agent-kit",
|
|
4662
|
+
["cursor-awareness", "--check", "--json", "--respect-prefs", "--stamp", "--cwd", root],
|
|
4663
|
+
{
|
|
4664
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4665
|
+
timeout: CURSOR_AWARENESS_SPAWN_TIMEOUT_MS,
|
|
4666
|
+
shell: false
|
|
4667
|
+
}
|
|
4668
|
+
);
|
|
4669
|
+
let out = "";
|
|
4670
|
+
child.stdout?.on("data", (chunk) => {
|
|
4671
|
+
out += chunk.toString("utf8");
|
|
4672
|
+
});
|
|
4673
|
+
child.on("error", () => {
|
|
4674
|
+
startFallback();
|
|
4675
|
+
});
|
|
4676
|
+
child.on("close", (code) => {
|
|
4677
|
+
if (settled || fallbackStarted) return;
|
|
4678
|
+
if (code !== 0 && !out.trim()) {
|
|
4679
|
+
startFallback();
|
|
4680
|
+
return;
|
|
4681
|
+
}
|
|
4682
|
+
try {
|
|
4683
|
+
finish(JSON.parse(out.trim()));
|
|
4684
|
+
} catch {
|
|
4685
|
+
startFallback();
|
|
4686
|
+
}
|
|
4687
|
+
});
|
|
4688
|
+
});
|
|
4689
|
+
if (!shouldEmitCursorAwarenessNudge(result)) return null;
|
|
4690
|
+
return CURSOR_AWARENESS_NUDGE;
|
|
4691
|
+
}
|
|
3856
4692
|
async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
3857
|
-
const root =
|
|
3858
|
-
const handoffPath =
|
|
4693
|
+
const root = path26.resolve(rootDir);
|
|
4694
|
+
const handoffPath = path26.join(root, ".cursor", "HANDOFF.md");
|
|
3859
4695
|
const handoffFull = await readFull(handoffPath);
|
|
3860
4696
|
const handoff = await readTextLimited(handoffPath);
|
|
3861
4697
|
const parts = [HARD_RULES];
|
|
@@ -3867,6 +4703,8 @@ async function buildSessionStartAdditionalContext(rootDir, _payload = {}) {
|
|
|
3867
4703
|
if (dogfood) parts.push(dogfood);
|
|
3868
4704
|
const updateNudge = await updateCheckSection(root);
|
|
3869
4705
|
if (updateNudge) parts.push(updateNudge);
|
|
4706
|
+
const cursorNudge = await cursorAwarenessSection(root);
|
|
4707
|
+
if (cursorNudge) parts.push(cursorNudge);
|
|
3870
4708
|
const formatWarnings = validateHandoffText(handoffFull);
|
|
3871
4709
|
if (formatWarnings.length) {
|
|
3872
4710
|
const bullet = formatWarnings.map((w) => `- ${w.message}`).join("\n");
|
|
@@ -3898,13 +4736,13 @@ function resolveSessionRoot(payload, cwd = process.cwd()) {
|
|
|
3898
4736
|
}
|
|
3899
4737
|
|
|
3900
4738
|
// src/commands/hook.ts
|
|
3901
|
-
var hookCommand =
|
|
4739
|
+
var hookCommand = defineCommand10({
|
|
3902
4740
|
meta: {
|
|
3903
4741
|
name: "hook",
|
|
3904
4742
|
description: "Cursor hook adapters (session-start, pre-compact). CLI is SoT; thin hooks shell out here."
|
|
3905
4743
|
},
|
|
3906
4744
|
subCommands: {
|
|
3907
|
-
"session-start":
|
|
4745
|
+
"session-start": defineCommand10({
|
|
3908
4746
|
meta: {
|
|
3909
4747
|
name: "session-start",
|
|
3910
4748
|
description: "Emit sessionStart additional_context JSON (stdin: Cursor payload)"
|
|
@@ -3918,12 +4756,12 @@ var hookCommand = defineCommand9({
|
|
|
3918
4756
|
async run({ args }) {
|
|
3919
4757
|
const payload = await readStdinJson();
|
|
3920
4758
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
3921
|
-
const root = resolveSessionRoot(payload,
|
|
4759
|
+
const root = resolveSessionRoot(payload, path27.resolve(cwd));
|
|
3922
4760
|
const out = await buildSessionStartAdditionalContext(root, payload);
|
|
3923
4761
|
console.log(JSON.stringify(out));
|
|
3924
4762
|
}
|
|
3925
4763
|
}),
|
|
3926
|
-
"pre-compact":
|
|
4764
|
+
"pre-compact": defineCommand10({
|
|
3927
4765
|
meta: {
|
|
3928
4766
|
name: "pre-compact",
|
|
3929
4767
|
description: "Emit preCompact user_message JSON (stdin: Cursor payload)"
|
|
@@ -3938,15 +4776,15 @@ var hookCommand = defineCommand9({
|
|
|
3938
4776
|
|
|
3939
4777
|
// src/commands/init.ts
|
|
3940
4778
|
import { intro, outro } from "@clack/prompts";
|
|
3941
|
-
import { defineCommand as
|
|
4779
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
3942
4780
|
|
|
3943
4781
|
// src/commands/install.ts
|
|
3944
|
-
import
|
|
3945
|
-
import { defineCommand as
|
|
4782
|
+
import path30 from "path";
|
|
4783
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
3946
4784
|
|
|
3947
4785
|
// src/generator/personalization.ts
|
|
3948
|
-
import { readFile as
|
|
3949
|
-
import
|
|
4786
|
+
import { readFile as readFile14, writeFile as writeFile6 } from "fs/promises";
|
|
4787
|
+
import path28 from "path";
|
|
3950
4788
|
var PERSONALIZATION_CONTRACT_VERSION = 1;
|
|
3951
4789
|
var CONTEXT_PATH = ".cursor/project-context.md";
|
|
3952
4790
|
var AGENTS_PATH = "AGENTS.md";
|
|
@@ -4097,7 +4935,7 @@ function renderProjectContext(profile) {
|
|
|
4097
4935
|
`;
|
|
4098
4936
|
}
|
|
4099
4937
|
async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
4100
|
-
const target =
|
|
4938
|
+
const target = path28.join(rootDir, relativePath);
|
|
4101
4939
|
if (await fileExists(target)) {
|
|
4102
4940
|
return {
|
|
4103
4941
|
kind: "file",
|
|
@@ -4107,8 +4945,8 @@ async function createOwnedFile(rootDir, relativePath, content, evidence) {
|
|
|
4107
4945
|
evidence
|
|
4108
4946
|
};
|
|
4109
4947
|
}
|
|
4110
|
-
await ensureDir(
|
|
4111
|
-
await
|
|
4948
|
+
await ensureDir(path28.dirname(target));
|
|
4949
|
+
await writeFile6(target, content, "utf8");
|
|
4112
4950
|
return {
|
|
4113
4951
|
kind: "file",
|
|
4114
4952
|
id: relativePath,
|
|
@@ -4124,7 +4962,7 @@ async function packTargets(registryRoot, packId) {
|
|
|
4124
4962
|
async function existingTargets(projectRoot, targets) {
|
|
4125
4963
|
const checks = await Promise.all(
|
|
4126
4964
|
targets.map(
|
|
4127
|
-
async (target) => await fileExists(
|
|
4965
|
+
async (target) => await fileExists(path28.join(projectRoot, target)) ? target : null
|
|
4128
4966
|
)
|
|
4129
4967
|
);
|
|
4130
4968
|
return checks.filter((target) => target !== null);
|
|
@@ -4146,14 +4984,14 @@ async function applyPersonalization(input) {
|
|
|
4146
4984
|
componentResults.push({ ...item, status: "unavailable" });
|
|
4147
4985
|
continue;
|
|
4148
4986
|
}
|
|
4149
|
-
const target =
|
|
4987
|
+
const target = path28.posix.join(
|
|
4150
4988
|
".cursor",
|
|
4151
4989
|
"skills",
|
|
4152
4990
|
skill.path.includes("/core/") ? "core" : "community",
|
|
4153
4991
|
skill.id,
|
|
4154
4992
|
"SKILL.md"
|
|
4155
4993
|
);
|
|
4156
|
-
if (await fileExists(
|
|
4994
|
+
if (await fileExists(path28.join(input.rootDir, target))) {
|
|
4157
4995
|
componentResults.push({ ...item, status: "skipped-customized", path: target });
|
|
4158
4996
|
protectedPaths.add(target);
|
|
4159
4997
|
continue;
|
|
@@ -4208,7 +5046,7 @@ async function applyPersonalization(input) {
|
|
|
4208
5046
|
items: [...fileResults, ...componentResults],
|
|
4209
5047
|
protectedPaths: [...protectedPaths].sort()
|
|
4210
5048
|
};
|
|
4211
|
-
await writeJson(
|
|
5049
|
+
await writeJson(path28.join(input.rootDir, RESULT_PATH), result);
|
|
4212
5050
|
return {
|
|
4213
5051
|
result,
|
|
4214
5052
|
manifest: {
|
|
@@ -4226,27 +5064,27 @@ async function applyPersonalization(input) {
|
|
|
4226
5064
|
};
|
|
4227
5065
|
}
|
|
4228
5066
|
async function readRepositoryProfile(rootDir) {
|
|
4229
|
-
const target =
|
|
5067
|
+
const target = path28.join(rootDir, ".cursor/agent-kit.config.json");
|
|
4230
5068
|
if (!await fileExists(target)) return null;
|
|
4231
|
-
return JSON.parse(await
|
|
5069
|
+
return JSON.parse(await readFile14(target, "utf8"));
|
|
4232
5070
|
}
|
|
4233
5071
|
|
|
4234
5072
|
// src/lifecycle/onboard-migration.ts
|
|
4235
|
-
import { createHash as
|
|
4236
|
-
import { readFile as
|
|
4237
|
-
import
|
|
5073
|
+
import { createHash as createHash4 } from "crypto";
|
|
5074
|
+
import { readFile as readFile15, unlink } from "fs/promises";
|
|
5075
|
+
import path29 from "path";
|
|
4238
5076
|
var LEGACY_ONBOARD_PATH = ".cursor/commands/onboard.md";
|
|
4239
5077
|
var NAMESPACED_ONBOARD_PATH = ".cursor/commands/agent-kit-onboard.md";
|
|
4240
5078
|
var MANAGED_LEGACY_HASHES = /* @__PURE__ */ new Set([
|
|
4241
5079
|
"b274a68941813f19b185893cb7c5561dff027f53270890029992f208e24992fe"
|
|
4242
5080
|
]);
|
|
4243
5081
|
async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_LEGACY_HASHES) {
|
|
4244
|
-
const legacyPath =
|
|
5082
|
+
const legacyPath = path29.join(projectRoot, LEGACY_ONBOARD_PATH);
|
|
4245
5083
|
if (!await fileExists(legacyPath)) return "absent";
|
|
4246
|
-
const namespacedPath =
|
|
5084
|
+
const namespacedPath = path29.join(projectRoot, NAMESPACED_ONBOARD_PATH);
|
|
4247
5085
|
if (!await fileExists(namespacedPath)) return "preserved-customized";
|
|
4248
|
-
const content = await
|
|
4249
|
-
const hash =
|
|
5086
|
+
const content = await readFile15(legacyPath);
|
|
5087
|
+
const hash = createHash4("sha256").update(content).digest("hex");
|
|
4250
5088
|
if (!managedHashes.has(hash)) return "preserved-customized";
|
|
4251
5089
|
await unlink(legacyPath);
|
|
4252
5090
|
return "removed-managed";
|
|
@@ -4255,16 +5093,20 @@ async function migrateLegacyOnboardCommand(projectRoot, managedHashes = MANAGED_
|
|
|
4255
5093
|
// src/lifecycle/sync.ts
|
|
4256
5094
|
async function installL0(registryRoot, projectRoot, protectedGlobs) {
|
|
4257
5095
|
const stats = emptyStats();
|
|
5096
|
+
const managedHashes = await loadManagedHashLedger(projectRoot);
|
|
5097
|
+
const copyOpts = { managedHashes, persistManagedHashes: false };
|
|
4258
5098
|
for (const artifact of L0_ARTIFACTS) {
|
|
4259
5099
|
const outcome = await copyRegistryFile(
|
|
4260
5100
|
registryRoot,
|
|
4261
5101
|
projectRoot,
|
|
4262
5102
|
artifact.source,
|
|
4263
5103
|
artifact.target,
|
|
4264
|
-
protectedGlobs
|
|
5104
|
+
protectedGlobs,
|
|
5105
|
+
copyOpts
|
|
4265
5106
|
);
|
|
4266
5107
|
recordOutcome(stats, artifact.target, outcome);
|
|
4267
5108
|
}
|
|
5109
|
+
await saveManagedHashLedger(projectRoot, managedHashes);
|
|
4268
5110
|
const migration = await migrateLegacyOnboardCommand(projectRoot);
|
|
4269
5111
|
if (migration === "removed-managed") {
|
|
4270
5112
|
stats.removed.push(".cursor/commands/onboard.md");
|
|
@@ -4314,7 +5156,7 @@ function printReadinessNarrative(result) {
|
|
|
4314
5156
|
);
|
|
4315
5157
|
}
|
|
4316
5158
|
async function performInstall(options) {
|
|
4317
|
-
const projectRoot =
|
|
5159
|
+
const projectRoot = path30.resolve(options.cwd);
|
|
4318
5160
|
const packs = parsePackList(options.pack);
|
|
4319
5161
|
const existing = await loadAgentKitManifest(projectRoot);
|
|
4320
5162
|
const registry = await resolveRegistryFromCli({
|
|
@@ -4368,7 +5210,7 @@ async function performInstall(options) {
|
|
|
4368
5210
|
safeChanges: readinessExecution.changes
|
|
4369
5211
|
};
|
|
4370
5212
|
}
|
|
4371
|
-
var installCommand =
|
|
5213
|
+
var installCommand = defineCommand11({
|
|
4372
5214
|
meta: {
|
|
4373
5215
|
name: "install",
|
|
4374
5216
|
description: "Bootstrap L0 (+ optional packs) from the registry and write agent-kit.json."
|
|
@@ -4390,7 +5232,7 @@ var installCommand = defineCommand10({
|
|
|
4390
5232
|
...REGISTRY_CLI_ARGS
|
|
4391
5233
|
},
|
|
4392
5234
|
async run({ args }) {
|
|
4393
|
-
const projectRoot =
|
|
5235
|
+
const projectRoot = path30.resolve(args.cwd);
|
|
4394
5236
|
logger.info(`Installing into: ${projectRoot}`);
|
|
4395
5237
|
const packs = parsePackList(args.pack);
|
|
4396
5238
|
for (const id of packs) {
|
|
@@ -4418,7 +5260,7 @@ var installCommand = defineCommand10({
|
|
|
4418
5260
|
async function runInitCompatibility(cwd, installer = performInstall) {
|
|
4419
5261
|
return installer({ cwd });
|
|
4420
5262
|
}
|
|
4421
|
-
var initCommand =
|
|
5263
|
+
var initCommand = defineCommand12({
|
|
4422
5264
|
meta: {
|
|
4423
5265
|
name: "init",
|
|
4424
5266
|
description: "Guided compatibility entry point for install and repository readiness."
|
|
@@ -4443,13 +5285,13 @@ var initCommand = defineCommand11({
|
|
|
4443
5285
|
});
|
|
4444
5286
|
|
|
4445
5287
|
// src/commands/monitors.ts
|
|
4446
|
-
import
|
|
4447
|
-
import { defineCommand as
|
|
5288
|
+
import path32 from "path";
|
|
5289
|
+
import { defineCommand as defineCommand13 } from "citty";
|
|
4448
5290
|
|
|
4449
5291
|
// src/invariants/monitors-untriaged.ts
|
|
4450
5292
|
import { execFile as execFile5 } from "child_process";
|
|
4451
|
-
import { readFile as
|
|
4452
|
-
import
|
|
5293
|
+
import { readFile as readFile16, readdir as readdir5, stat as stat2 } from "fs/promises";
|
|
5294
|
+
import path31 from "path";
|
|
4453
5295
|
import { promisify as promisify5 } from "util";
|
|
4454
5296
|
|
|
4455
5297
|
// src/invariants/triage-heading.ts
|
|
@@ -4473,7 +5315,7 @@ function hasOpenGaps(content) {
|
|
|
4473
5315
|
}
|
|
4474
5316
|
async function listMonitorFiles(memoryDir) {
|
|
4475
5317
|
try {
|
|
4476
|
-
const names = await
|
|
5318
|
+
const names = await readdir5(memoryDir);
|
|
4477
5319
|
return names.filter((n) => n.startsWith("plan-monitor-") && n.endsWith(".md")).sort();
|
|
4478
5320
|
} catch {
|
|
4479
5321
|
return [];
|
|
@@ -4490,7 +5332,7 @@ async function gitFreshMonitorNames(rootDir) {
|
|
|
4490
5332
|
for (const line of stdout.split("\n")) {
|
|
4491
5333
|
if (!line.trim()) continue;
|
|
4492
5334
|
const file = line.slice(3).trim().replace(/^.* -> /, "");
|
|
4493
|
-
const base =
|
|
5335
|
+
const base = path31.basename(file);
|
|
4494
5336
|
if (base.startsWith("plan-monitor-") && base.endsWith(".md")) {
|
|
4495
5337
|
names.add(base);
|
|
4496
5338
|
}
|
|
@@ -4513,15 +5355,15 @@ function monitorSlugFromName(fileName) {
|
|
|
4513
5355
|
return fileName.replace(/^plan-monitor-/, "").replace(/\.md$/, "").toLowerCase();
|
|
4514
5356
|
}
|
|
4515
5357
|
async function selectUntriagedMonitors(rootDir) {
|
|
4516
|
-
const root =
|
|
4517
|
-
const memoryDir =
|
|
5358
|
+
const root = path31.resolve(rootDir);
|
|
5359
|
+
const memoryDir = path31.join(root, ".cursor", "memory");
|
|
4518
5360
|
const allNames = await listMonitorFiles(memoryDir);
|
|
4519
5361
|
const selectionOrder = ["git-fresh", "handoff-aligned", "untriaged-scan"];
|
|
4520
5362
|
const byName = /* @__PURE__ */ new Map();
|
|
4521
5363
|
for (const name of allNames) {
|
|
4522
|
-
const abs =
|
|
5364
|
+
const abs = path31.join(memoryDir, name);
|
|
4523
5365
|
try {
|
|
4524
|
-
const [content, st] = await Promise.all([
|
|
5366
|
+
const [content, st] = await Promise.all([readFile16(abs, "utf8"), stat2(abs)]);
|
|
4525
5367
|
byName.set(name, { content, mtimeMs: st.mtimeMs });
|
|
4526
5368
|
} catch {
|
|
4527
5369
|
}
|
|
@@ -4534,7 +5376,7 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
4534
5376
|
const gitFreshSet = [...gitFresh].filter(untriaged).sort();
|
|
4535
5377
|
let handoff = "";
|
|
4536
5378
|
try {
|
|
4537
|
-
handoff = await
|
|
5379
|
+
handoff = await readFile16(path31.join(root, ".cursor", "HANDOFF.md"), "utf8");
|
|
4538
5380
|
} catch {
|
|
4539
5381
|
handoff = "";
|
|
4540
5382
|
}
|
|
@@ -4554,8 +5396,8 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
4554
5396
|
const row = byName.get(name);
|
|
4555
5397
|
if (!row) continue;
|
|
4556
5398
|
entries.push({
|
|
4557
|
-
path:
|
|
4558
|
-
relativePath:
|
|
5399
|
+
path: path31.join(memoryDir, name),
|
|
5400
|
+
relativePath: path31.relative(root, path31.join(memoryDir, name)).split(path31.sep).join("/"),
|
|
4559
5401
|
mtimeMs: row.mtimeMs,
|
|
4560
5402
|
hasTriageHeading: false,
|
|
4561
5403
|
hasOpenGaps: hasOpenGaps(row.content),
|
|
@@ -4574,7 +5416,7 @@ async function selectUntriagedMonitors(rootDir) {
|
|
|
4574
5416
|
}
|
|
4575
5417
|
|
|
4576
5418
|
// src/commands/monitors.ts
|
|
4577
|
-
var monitorsCommand =
|
|
5419
|
+
var monitorsCommand = defineCommand13({
|
|
4578
5420
|
meta: {
|
|
4579
5421
|
name: "monitors",
|
|
4580
5422
|
description: "Plan-monitor selection helpers (untriaged SoT for /plan-review-triage)"
|
|
@@ -4601,7 +5443,7 @@ var monitorsCommand = defineCommand12({
|
|
|
4601
5443
|
process.exitCode = 2;
|
|
4602
5444
|
return;
|
|
4603
5445
|
}
|
|
4604
|
-
const result = await selectUntriagedMonitors(
|
|
5446
|
+
const result = await selectUntriagedMonitors(path32.resolve(args.cwd));
|
|
4605
5447
|
if (args.json) {
|
|
4606
5448
|
console.log(JSON.stringify(result, null, 2));
|
|
4607
5449
|
return;
|
|
@@ -4617,8 +5459,8 @@ var monitorsCommand = defineCommand12({
|
|
|
4617
5459
|
});
|
|
4618
5460
|
|
|
4619
5461
|
// src/commands/run-plan.ts
|
|
4620
|
-
import
|
|
4621
|
-
import { defineCommand as
|
|
5462
|
+
import path37 from "path";
|
|
5463
|
+
import { defineCommand as defineCommand14 } from "citty";
|
|
4622
5464
|
|
|
4623
5465
|
// src/plan-loop/backends.ts
|
|
4624
5466
|
import { execFileSync as execFileSync2, spawn as spawn5 } from "child_process";
|
|
@@ -4702,14 +5544,14 @@ function listBackendIds() {
|
|
|
4702
5544
|
}
|
|
4703
5545
|
|
|
4704
5546
|
// src/plan-loop/run-loop.ts
|
|
4705
|
-
import { mkdir as
|
|
4706
|
-
import
|
|
5547
|
+
import { mkdir as mkdir5, readFile as readFile19, rm, unlink as unlink2 } from "fs/promises";
|
|
5548
|
+
import path36 from "path";
|
|
4707
5549
|
|
|
4708
5550
|
// src/plan-loop/external-review.ts
|
|
4709
5551
|
import { spawn as spawn6 } from "child_process";
|
|
4710
|
-
import
|
|
4711
|
-
var CANONICAL_REL =
|
|
4712
|
-
var FALLBACK_REL =
|
|
5552
|
+
import path33 from "path";
|
|
5553
|
+
var CANONICAL_REL = path33.join(".cursor", "scripts", "plan-external-review.sh");
|
|
5554
|
+
var FALLBACK_REL = path33.join("scripts", "plan-external-review.sh");
|
|
4713
5555
|
function isPlanExhaustedReason(reason) {
|
|
4714
5556
|
const r = reason.trim().toLowerCase();
|
|
4715
5557
|
if (!r) return false;
|
|
@@ -4731,8 +5573,8 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
4731
5573
|
const existsFn = options.existsFn ?? fileExists;
|
|
4732
5574
|
const log = options.log ?? ((line) => console.log(line));
|
|
4733
5575
|
const force = options.force === true;
|
|
4734
|
-
const canonicalPath =
|
|
4735
|
-
const fallbackPath =
|
|
5576
|
+
const canonicalPath = path33.join(root, CANONICAL_REL);
|
|
5577
|
+
const fallbackPath = path33.join(root, FALLBACK_REL);
|
|
4736
5578
|
let scriptPath = null;
|
|
4737
5579
|
let scriptRel = CANONICAL_REL;
|
|
4738
5580
|
if (await existsFn(canonicalPath)) {
|
|
@@ -4785,7 +5627,7 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
4785
5627
|
}
|
|
4786
5628
|
|
|
4787
5629
|
// src/plan-loop/persona-banners.ts
|
|
4788
|
-
import
|
|
5630
|
+
import path34 from "path";
|
|
4789
5631
|
import {
|
|
4790
5632
|
blue,
|
|
4791
5633
|
cyan as cyan2,
|
|
@@ -4821,7 +5663,7 @@ function resolveColor(name, fallback) {
|
|
|
4821
5663
|
async function resolveCliPersonaId(root) {
|
|
4822
5664
|
try {
|
|
4823
5665
|
const cfg = await readJson(
|
|
4824
|
-
|
|
5666
|
+
path34.join(root, ".cursor", "context", "config.json")
|
|
4825
5667
|
);
|
|
4826
5668
|
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
4827
5669
|
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
@@ -4832,7 +5674,7 @@ async function resolveCliPersonaId(root) {
|
|
|
4832
5674
|
}
|
|
4833
5675
|
async function loadPersonaPack(root, personaId) {
|
|
4834
5676
|
try {
|
|
4835
|
-
const personaPath =
|
|
5677
|
+
const personaPath = path34.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
4836
5678
|
const pack = await readJson(personaPath);
|
|
4837
5679
|
if (!pack || typeof pack.id !== "string") return null;
|
|
4838
5680
|
return pack;
|
|
@@ -4881,8 +5723,8 @@ function createPersonaBannerPrinter(persona) {
|
|
|
4881
5723
|
}
|
|
4882
5724
|
|
|
4883
5725
|
// src/plan-loop/plan-state.ts
|
|
4884
|
-
import { readFile as
|
|
4885
|
-
import
|
|
5726
|
+
import { readFile as readFile17, readdir as readdir6 } from "fs/promises";
|
|
5727
|
+
import path35 from "path";
|
|
4886
5728
|
function countPendingTodos(raw) {
|
|
4887
5729
|
const lines = raw.split(/\r?\n/);
|
|
4888
5730
|
let inFront = 0;
|
|
@@ -4909,15 +5751,15 @@ function countPendingTodos(raw) {
|
|
|
4909
5751
|
}
|
|
4910
5752
|
async function findActivePlanFile(plansDir) {
|
|
4911
5753
|
if (!await fileExists(plansDir)) return null;
|
|
4912
|
-
const files = (await
|
|
4913
|
-
return files[0] ?
|
|
5754
|
+
const files = (await readdir6(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
|
|
5755
|
+
return files[0] ? path35.join(plansDir, files[0]) : null;
|
|
4914
5756
|
}
|
|
4915
5757
|
async function readPlan(planPath) {
|
|
4916
|
-
return
|
|
5758
|
+
return readFile17(planPath, "utf8");
|
|
4917
5759
|
}
|
|
4918
5760
|
|
|
4919
5761
|
// src/plan-loop/sentinel.ts
|
|
4920
|
-
import { readFile as
|
|
5762
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
4921
5763
|
var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—\-].*)?)/i;
|
|
4922
5764
|
function takeFromText(text) {
|
|
4923
5765
|
if (!text) return null;
|
|
@@ -4964,7 +5806,7 @@ function parseSentinelFromLog(content) {
|
|
|
4964
5806
|
}
|
|
4965
5807
|
async function parseSentinelFromLogFile(logPath) {
|
|
4966
5808
|
try {
|
|
4967
|
-
const content = await
|
|
5809
|
+
const content = await readFile18(logPath, "utf8");
|
|
4968
5810
|
return parseSentinelFromLog(content);
|
|
4969
5811
|
} catch {
|
|
4970
5812
|
return { kind: "missing" };
|
|
@@ -4987,16 +5829,16 @@ function sleep(ms) {
|
|
|
4987
5829
|
return new Promise((r) => setTimeout(r, ms));
|
|
4988
5830
|
}
|
|
4989
5831
|
async function runPlanLoop(opts) {
|
|
4990
|
-
const plansDir =
|
|
4991
|
-
const stopFile =
|
|
4992
|
-
const logDir =
|
|
5832
|
+
const plansDir = path36.join(opts.root, ".cursor", "plans");
|
|
5833
|
+
const stopFile = path36.join(opts.root, ".cursor", "loop.stop");
|
|
5834
|
+
const logDir = path36.join(opts.root, ".cursor", "loop-logs");
|
|
4993
5835
|
const planPath = await findActivePlanFile(plansDir);
|
|
4994
5836
|
if (!planPath) {
|
|
4995
5837
|
logger.error("No active plan in .cursor/plans/");
|
|
4996
5838
|
return 1;
|
|
4997
5839
|
}
|
|
4998
5840
|
const pending = async () => countPendingTodos(await readPlan(planPath));
|
|
4999
|
-
await
|
|
5841
|
+
await mkdir5(logDir, { recursive: true });
|
|
5000
5842
|
try {
|
|
5001
5843
|
await unlink2(stopFile);
|
|
5002
5844
|
} catch {
|
|
@@ -5010,7 +5852,7 @@ async function runPlanLoop(opts) {
|
|
|
5010
5852
|
try {
|
|
5011
5853
|
const persona = await loadCliRunPlanPersona(opts.root);
|
|
5012
5854
|
const banners = createPersonaBannerPrinter(persona);
|
|
5013
|
-
console.log(`Active plan: ${
|
|
5855
|
+
console.log(`Active plan: ${path36.basename(planPath)}`);
|
|
5014
5856
|
console.log(`Pending to-dos: ${await pending()} | max ticks: ${opts.maxTicks}`);
|
|
5015
5857
|
console.log(`Backend: ${opts.backend.id}`);
|
|
5016
5858
|
if (persona) {
|
|
@@ -5053,8 +5895,8 @@ async function runPlanLoop(opts) {
|
|
|
5053
5895
|
planExhausted = true;
|
|
5054
5896
|
break;
|
|
5055
5897
|
}
|
|
5056
|
-
const logPath =
|
|
5057
|
-
const relLog =
|
|
5898
|
+
const logPath = path36.join(logDir, `tick-${stamp()}.log`);
|
|
5899
|
+
const relLog = path36.relative(opts.root, logPath);
|
|
5058
5900
|
console.log("");
|
|
5059
5901
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
5060
5902
|
if (banners) banners.tickStart(tickLine);
|
|
@@ -5073,7 +5915,7 @@ async function runPlanLoop(opts) {
|
|
|
5073
5915
|
return 1;
|
|
5074
5916
|
}
|
|
5075
5917
|
try {
|
|
5076
|
-
const logText = await
|
|
5918
|
+
const logText = await readFile19(logPath, "utf8");
|
|
5077
5919
|
if (logText.includes("Too many MCP tools")) {
|
|
5078
5920
|
const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
|
|
5079
5921
|
if (banners) banners.stop(msg);
|
|
@@ -5130,7 +5972,7 @@ async function runPlanLoop(opts) {
|
|
|
5130
5972
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
5131
5973
|
if (banners) banners.phaseComplete(finishDetail);
|
|
5132
5974
|
console.log(
|
|
5133
|
-
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${
|
|
5975
|
+
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path36.relative(opts.root, logDir)}/`
|
|
5134
5976
|
);
|
|
5135
5977
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
5136
5978
|
await armExternalPlanReview(opts.root);
|
|
@@ -5142,7 +5984,7 @@ async function runPlanLoop(opts) {
|
|
|
5142
5984
|
}
|
|
5143
5985
|
|
|
5144
5986
|
// src/commands/run-plan.ts
|
|
5145
|
-
var runPlanCommand =
|
|
5987
|
+
var runPlanCommand = defineCommand14({
|
|
5146
5988
|
meta: {
|
|
5147
5989
|
name: "run-plan",
|
|
5148
5990
|
description: "Headless continuous plan runner: one fresh agent per tick (LOOP_TICK_RESULT contract). Never git-prod."
|
|
@@ -5201,7 +6043,7 @@ var runPlanCommand = defineCommand13({
|
|
|
5201
6043
|
return;
|
|
5202
6044
|
}
|
|
5203
6045
|
const code = await runPlanLoop({
|
|
5204
|
-
root:
|
|
6046
|
+
root: path37.resolve(args.cwd),
|
|
5205
6047
|
maxTicks,
|
|
5206
6048
|
sleepSeconds,
|
|
5207
6049
|
model: args.model ? String(args.model) : void 0,
|
|
@@ -5213,8 +6055,8 @@ var runPlanCommand = defineCommand13({
|
|
|
5213
6055
|
});
|
|
5214
6056
|
|
|
5215
6057
|
// src/commands/scan.ts
|
|
5216
|
-
import { defineCommand as
|
|
5217
|
-
var scanCommand =
|
|
6058
|
+
import { defineCommand as defineCommand15 } from "citty";
|
|
6059
|
+
var scanCommand = defineCommand15({
|
|
5218
6060
|
meta: {
|
|
5219
6061
|
name: "scan",
|
|
5220
6062
|
description: "Scan the current repository and print detected profile."
|
|
@@ -5235,8 +6077,8 @@ var scanCommand = defineCommand14({
|
|
|
5235
6077
|
});
|
|
5236
6078
|
|
|
5237
6079
|
// src/commands/status.ts
|
|
5238
|
-
import
|
|
5239
|
-
import { defineCommand as
|
|
6080
|
+
import path38 from "path";
|
|
6081
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
5240
6082
|
function profileStatus(profile) {
|
|
5241
6083
|
if (!profile) return { origin: "none", evidence: [], profile: null };
|
|
5242
6084
|
if ("detection" in profile && profile.detection && typeof profile.detection === "object") {
|
|
@@ -5249,7 +6091,7 @@ function profileStatus(profile) {
|
|
|
5249
6091
|
}
|
|
5250
6092
|
return { origin: "legacy-wizard", evidence: [], profile };
|
|
5251
6093
|
}
|
|
5252
|
-
var statusCommand =
|
|
6094
|
+
var statusCommand = defineCommand16({
|
|
5253
6095
|
meta: {
|
|
5254
6096
|
name: "status",
|
|
5255
6097
|
description: "Show Agent Kit distribution status (manifest + optional wizard profile)."
|
|
@@ -5266,11 +6108,11 @@ var statusCommand = defineCommand15({
|
|
|
5266
6108
|
}
|
|
5267
6109
|
},
|
|
5268
6110
|
async run({ args }) {
|
|
5269
|
-
const rootDir =
|
|
6111
|
+
const rootDir = path38.resolve(args.cwd);
|
|
5270
6112
|
const [manifest, rawProfile, scan] = await Promise.all([
|
|
5271
6113
|
loadAgentKitManifest(rootDir),
|
|
5272
6114
|
readJson(
|
|
5273
|
-
|
|
6115
|
+
path38.join(rootDir, ".cursor", "agent-kit.config.json")
|
|
5274
6116
|
),
|
|
5275
6117
|
runScanner(rootDir)
|
|
5276
6118
|
]);
|
|
@@ -5325,11 +6167,11 @@ var statusCommand = defineCommand15({
|
|
|
5325
6167
|
});
|
|
5326
6168
|
|
|
5327
6169
|
// src/commands/update.ts
|
|
5328
|
-
import { defineCommand as
|
|
6170
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
5329
6171
|
|
|
5330
6172
|
// src/lifecycle/check-updates.ts
|
|
5331
6173
|
import { execFile as execFile6 } from "child_process";
|
|
5332
|
-
import
|
|
6174
|
+
import path39 from "path";
|
|
5333
6175
|
import { promisify as promisify6 } from "util";
|
|
5334
6176
|
var execFileAsync5 = promisify6(execFile6);
|
|
5335
6177
|
var SEMVER_CORE = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/i;
|
|
@@ -5412,20 +6254,20 @@ function readUpdateCheckPrefs(config) {
|
|
|
5412
6254
|
lastCheckedAt: typeof uc.lastCheckedAt === "string" ? uc.lastCheckedAt : null
|
|
5413
6255
|
};
|
|
5414
6256
|
}
|
|
5415
|
-
function
|
|
6257
|
+
function intervalElapsed2(lastCheckedAt, intervalDays) {
|
|
5416
6258
|
if (!lastCheckedAt) return true;
|
|
5417
6259
|
const last = Date.parse(lastCheckedAt);
|
|
5418
6260
|
if (Number.isNaN(last)) return true;
|
|
5419
6261
|
const ms = intervalDays * 24 * 60 * 60 * 1e3;
|
|
5420
6262
|
return Date.now() - last >= ms;
|
|
5421
6263
|
}
|
|
5422
|
-
async function
|
|
5423
|
-
const configPath =
|
|
6264
|
+
async function loadContextConfig2(cwd) {
|
|
6265
|
+
const configPath = path39.join(cwd, ".cursor", "context", "config.json");
|
|
5424
6266
|
return readJson(configPath);
|
|
5425
6267
|
}
|
|
5426
6268
|
async function stampLastCheckedAt(cwd) {
|
|
5427
|
-
const configPath =
|
|
5428
|
-
const existing = await
|
|
6269
|
+
const configPath = path39.join(cwd, ".cursor", "context", "config.json");
|
|
6270
|
+
const existing = await loadContextConfig2(cwd) ?? {};
|
|
5429
6271
|
const prev = existing.updateCheck && typeof existing.updateCheck === "object" ? { ...existing.updateCheck } : {};
|
|
5430
6272
|
existing.updateCheck = {
|
|
5431
6273
|
...DEFAULT_UPDATE_CHECK,
|
|
@@ -5461,7 +6303,7 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
5461
6303
|
};
|
|
5462
6304
|
}
|
|
5463
6305
|
if (options.respectPrefs) {
|
|
5464
|
-
const config = await
|
|
6306
|
+
const config = await loadContextConfig2(cwd);
|
|
5465
6307
|
const prefs = readUpdateCheckPrefs(config);
|
|
5466
6308
|
if (!prefs.enabled) {
|
|
5467
6309
|
return {
|
|
@@ -5474,7 +6316,7 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
5474
6316
|
message: "updateCheck.enabled is false (opt-in). Set true in .cursor/context/config.json to nudge."
|
|
5475
6317
|
};
|
|
5476
6318
|
}
|
|
5477
|
-
if (!
|
|
6319
|
+
if (!intervalElapsed2(prefs.lastCheckedAt, prefs.intervalDays)) {
|
|
5478
6320
|
return {
|
|
5479
6321
|
status: "skipped-interval",
|
|
5480
6322
|
installedVersion: manifest.version,
|
|
@@ -5565,7 +6407,7 @@ async function checkForUpdates(cwd, options = {}) {
|
|
|
5565
6407
|
}
|
|
5566
6408
|
|
|
5567
6409
|
// src/commands/update.ts
|
|
5568
|
-
var updateCommand =
|
|
6410
|
+
var updateCommand = defineCommand17({
|
|
5569
6411
|
meta: {
|
|
5570
6412
|
name: "update",
|
|
5571
6413
|
description: "Re-apply L0/packs/skills from the registry; never overwrites L3 protected paths. Use --check for notify-only."
|
|
@@ -5595,6 +6437,11 @@ var updateCommand = defineCommand16({
|
|
|
5595
6437
|
description: "Persist updateCheck.lastCheckedAt after a network check",
|
|
5596
6438
|
default: false
|
|
5597
6439
|
},
|
|
6440
|
+
"seed-overlay": {
|
|
6441
|
+
type: "boolean",
|
|
6442
|
+
description: "Seed the managed-hash ledger from current local overlay files before applying (factory/dogfood only; consumers should not use this)",
|
|
6443
|
+
default: false
|
|
6444
|
+
},
|
|
5598
6445
|
...REGISTRY_CLI_ARGS
|
|
5599
6446
|
},
|
|
5600
6447
|
async run({ args }) {
|
|
@@ -5636,10 +6483,18 @@ var updateCommand = defineCommand16({
|
|
|
5636
6483
|
packs: existing.packs,
|
|
5637
6484
|
skills: existing.skills,
|
|
5638
6485
|
protected: existing.protected,
|
|
6486
|
+
personalization: existing.personalization,
|
|
5639
6487
|
registryUrl: registry.url ?? existing.registry?.url,
|
|
5640
6488
|
registryRef: registry.ref ?? existing.registry?.ref
|
|
5641
6489
|
});
|
|
5642
6490
|
if (existing.overrides?.length) next.overrides = existing.overrides;
|
|
6491
|
+
if (next.version === existing.version && existing.installedAt) {
|
|
6492
|
+
next.installedAt = existing.installedAt;
|
|
6493
|
+
}
|
|
6494
|
+
if (args["seed-overlay"]) {
|
|
6495
|
+
await seedManagedHashLedger(args.cwd);
|
|
6496
|
+
logger.info("Seeded managed-hash ledger from current local overlay files.");
|
|
6497
|
+
}
|
|
5643
6498
|
const stats = await syncFromManifest(registry.root, args.cwd, next);
|
|
5644
6499
|
await saveManifest(args.cwd, next);
|
|
5645
6500
|
logApplyStats(stats);
|
|
@@ -5648,9 +6503,9 @@ var updateCommand = defineCommand16({
|
|
|
5648
6503
|
});
|
|
5649
6504
|
|
|
5650
6505
|
// src/commands/validate.ts
|
|
5651
|
-
import { readFile as
|
|
5652
|
-
import
|
|
5653
|
-
import { defineCommand as
|
|
6506
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
6507
|
+
import path40 from "path";
|
|
6508
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
5654
6509
|
|
|
5655
6510
|
// src/invariants/plan-schema.ts
|
|
5656
6511
|
var CITE5 = "agent-kit validate plan (.cursor/context/templates/plan.md)";
|
|
@@ -5696,9 +6551,9 @@ function validatePlanFrontmatterText(text) {
|
|
|
5696
6551
|
// src/commands/validate.ts
|
|
5697
6552
|
async function resolveEditedPath(cwd, explicit) {
|
|
5698
6553
|
if (explicit) {
|
|
5699
|
-
const filePath2 =
|
|
6554
|
+
const filePath2 = path40.resolve(cwd, explicit);
|
|
5700
6555
|
try {
|
|
5701
|
-
return { filePath: filePath2, content: await
|
|
6556
|
+
return { filePath: filePath2, content: await readFile20(filePath2, "utf8") };
|
|
5702
6557
|
} catch {
|
|
5703
6558
|
return null;
|
|
5704
6559
|
}
|
|
@@ -5706,9 +6561,9 @@ async function resolveEditedPath(cwd, explicit) {
|
|
|
5706
6561
|
const payload = await readStdinJson();
|
|
5707
6562
|
const rel = typeof payload.file_path === "string" && payload.file_path || typeof payload.path === "string" && payload.path || typeof payload.file === "string" && payload.file || "";
|
|
5708
6563
|
if (!rel) return null;
|
|
5709
|
-
const filePath =
|
|
6564
|
+
const filePath = path40.isAbsolute(rel) ? rel : path40.resolve(cwd, rel);
|
|
5710
6565
|
try {
|
|
5711
|
-
return { filePath, content: await
|
|
6566
|
+
return { filePath, content: await readFile20(filePath, "utf8") };
|
|
5712
6567
|
} catch {
|
|
5713
6568
|
return null;
|
|
5714
6569
|
}
|
|
@@ -5720,13 +6575,13 @@ function isPlanPath(filePath) {
|
|
|
5720
6575
|
const norm = filePath.replace(/\\/g, "/");
|
|
5721
6576
|
return norm.includes("/.cursor/plans/") && norm.endsWith(".plan.md");
|
|
5722
6577
|
}
|
|
5723
|
-
var validateCommand =
|
|
6578
|
+
var validateCommand = defineCommand18({
|
|
5724
6579
|
meta: {
|
|
5725
6580
|
name: "validate",
|
|
5726
6581
|
description: "Advisory validators for HANDOFF / plan frontmatter (afterFileEdit adapter)"
|
|
5727
6582
|
},
|
|
5728
6583
|
subCommands: {
|
|
5729
|
-
handoff:
|
|
6584
|
+
handoff: defineCommand18({
|
|
5730
6585
|
meta: { name: "handoff", description: "Validate HANDOFF machine fields" },
|
|
5731
6586
|
args: {
|
|
5732
6587
|
cwd: { type: "string", default: process.cwd() },
|
|
@@ -5736,10 +6591,10 @@ var validateCommand = defineCommand17({
|
|
|
5736
6591
|
async run({ args }) {
|
|
5737
6592
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
5738
6593
|
const fileArg = typeof args.file === "string" ? args.file : void 0;
|
|
5739
|
-
const filePath = fileArg ?
|
|
6594
|
+
const filePath = fileArg ? path40.resolve(cwd, fileArg) : path40.join(path40.resolve(cwd), ".cursor", "HANDOFF.md");
|
|
5740
6595
|
let content = "";
|
|
5741
6596
|
try {
|
|
5742
|
-
content = await
|
|
6597
|
+
content = await readFile20(filePath, "utf8");
|
|
5743
6598
|
} catch {
|
|
5744
6599
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
5745
6600
|
return;
|
|
@@ -5748,7 +6603,7 @@ var validateCommand = defineCommand17({
|
|
|
5748
6603
|
console.log(JSON.stringify({ ok: warnings.length === 0, warnings }));
|
|
5749
6604
|
}
|
|
5750
6605
|
}),
|
|
5751
|
-
plan:
|
|
6606
|
+
plan: defineCommand18({
|
|
5752
6607
|
meta: { name: "plan", description: "Validate plan frontmatter" },
|
|
5753
6608
|
args: {
|
|
5754
6609
|
cwd: { type: "string", default: process.cwd() },
|
|
@@ -5763,10 +6618,10 @@ var validateCommand = defineCommand17({
|
|
|
5763
6618
|
process.exitCode = 2;
|
|
5764
6619
|
return;
|
|
5765
6620
|
}
|
|
5766
|
-
const filePath =
|
|
6621
|
+
const filePath = path40.resolve(cwd, fileArg);
|
|
5767
6622
|
let content = "";
|
|
5768
6623
|
try {
|
|
5769
|
-
content = await
|
|
6624
|
+
content = await readFile20(filePath, "utf8");
|
|
5770
6625
|
} catch {
|
|
5771
6626
|
console.log(JSON.stringify({ ok: true, warnings: [], note: "file missing" }));
|
|
5772
6627
|
return;
|
|
@@ -5775,7 +6630,7 @@ var validateCommand = defineCommand17({
|
|
|
5775
6630
|
console.log(JSON.stringify({ ok: warnings.length === 0, warnings }));
|
|
5776
6631
|
}
|
|
5777
6632
|
}),
|
|
5778
|
-
"after-edit":
|
|
6633
|
+
"after-edit": defineCommand18({
|
|
5779
6634
|
meta: {
|
|
5780
6635
|
name: "after-edit",
|
|
5781
6636
|
description: "Advisory afterFileEdit: annotate HANDOFF/plan issues (never block)"
|
|
@@ -5785,7 +6640,7 @@ var validateCommand = defineCommand17({
|
|
|
5785
6640
|
},
|
|
5786
6641
|
async run({ args }) {
|
|
5787
6642
|
const cwd = typeof args.cwd === "string" ? args.cwd : process.cwd();
|
|
5788
|
-
const resolved = await resolveEditedPath(
|
|
6643
|
+
const resolved = await resolveEditedPath(path40.resolve(cwd));
|
|
5789
6644
|
if (!resolved) {
|
|
5790
6645
|
console.log(JSON.stringify({}));
|
|
5791
6646
|
return;
|
|
@@ -5828,10 +6683,11 @@ var validateCommand = defineCommand17({
|
|
|
5828
6683
|
});
|
|
5829
6684
|
|
|
5830
6685
|
// src/index.ts
|
|
5831
|
-
var main =
|
|
6686
|
+
var main = defineCommand19({
|
|
5832
6687
|
meta: {
|
|
5833
6688
|
name: "agent-kit",
|
|
5834
|
-
description: "HITL framework for AI-assisted IDEs"
|
|
6689
|
+
description: "HITL framework for AI-assisted IDEs",
|
|
6690
|
+
version: KIT_VERSION
|
|
5835
6691
|
},
|
|
5836
6692
|
subCommands: {
|
|
5837
6693
|
init: initCommand,
|
|
@@ -5841,6 +6697,7 @@ var main = defineCommand18({
|
|
|
5841
6697
|
doctor: doctorCommand,
|
|
5842
6698
|
status: statusCommand,
|
|
5843
6699
|
update: updateCommand,
|
|
6700
|
+
"cursor-awareness": cursorAwarenessCommand,
|
|
5844
6701
|
diff: diffCommand,
|
|
5845
6702
|
contribute: contributeCommand,
|
|
5846
6703
|
handoff: handoffCommand,
|