@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/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 defineCommand18, runMain } from "citty";
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 mkdir2, readFile as readFile2 } from "fs/promises";
11
- import path3 from "path";
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(path3.sep).join("/");
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 readFile2(sourceAbs);
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 readFile2(targetAbs, "utf8");
316
+ existing = await readFile3(targetAbs, "utf8");
188
317
  } catch {
189
318
  existing = null;
190
319
  }
191
- const next = await readFile2(sourceAbs, "utf8");
192
- if (existing === next) return "unchanged";
193
- await mkdir2(path3.dirname(targetAbs), { recursive: true });
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(path3.sep).join("/");
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 = path3.join(projectRoot, MANIFEST_RELATIVE_PATH);
394
+ const target = path4.join(projectRoot, MANIFEST_RELATIVE_PATH);
216
395
  const payload = {
217
396
  ...manifest,
218
397
  schemaVersion: MANIFEST_SCHEMA_VERSION,
219
- installedAt: (/* @__PURE__ */ new Date()).toISOString()
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 path4 from "path";
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 createHash("sha256").update(`${url}@${ref}`).digest("hex").slice(0, 16);
501
+ return createHash2("sha256").update(`${url}@${ref}`).digest("hex").slice(0, 16);
315
502
  }
316
503
  async function hasRegistryIndex(root) {
317
- return fileExists(path4.join(root, "registry", "registry.json"));
504
+ return fileExists(path5.join(root, "registry", "registry.json"));
318
505
  }
319
506
  async function cloneRegistry(url, ref, dest) {
320
- const { mkdir: mkdir5 } = await import("fs/promises");
321
- await mkdir5(path4.dirname(dest), { recursive: true });
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 = path4.resolve(options.registryPath);
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 = path4.resolve(envPath);
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: path4.resolve(options.cwd),
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 = path4.join(homedir(), ".cache", "agent-kit", "registry", cacheKey(url, ref));
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 path5 from "path";
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 = path5.join(cwd, MANIFEST_RELATIVE_PATH);
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 path6 from "path";
802
+ import path7 from "path";
616
803
  async function loadRegistry(rootDir) {
617
- const indexPath = path6.join(rootDir, "registry", "registry.json");
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 readFile3 } from "fs/promises";
643
- import path7 from "path";
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: path7.posix.join(member.source, "SKILL.md"),
650
- targetRel: path7.posix.join(".cursor", "skills", category, member.id, "SKILL.md")
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: path7.posix.join(".cursor", "rules", path7.posix.basename(member.source))
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: path7.posix.join(".cursor", "agents", path7.posix.basename(member.source))
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: path7.posix.join(".cursor", "commands", path7.posix.basename(member.source))
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 : path7.posix.join(".cursor", "hooks", path7.posix.basename(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 = path7.posix.basename(member.source);
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: path7.posix.join(".cursor", "context", "templates", base)
864
+ targetRel: path8.posix.join(".cursor", "context", "templates", base)
678
865
  };
679
866
  }
680
867
  return {
681
868
  sourceRel: member.source,
682
- targetRel: path7.posix.join("templates", base)
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 = path7.posix.join(skill.path, "SKILL.md");
695
- const targetRel = path7.posix.join(".cursor", "skills", category, skill.id, "SKILL.md");
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(path7.join(registryRoot, "registry", "registry.json"));
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, path7.join(summary.path, "pack.json"));
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 mkdir3, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
857
- import path8 from "path";
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 readFile4(resolveContained(projectRoot, rel), "utf8");
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(path8.sep).join("/"), a.source.split(path8.sep).join("/"));
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(path8.sep).join("/"), sourceRel.split(path8.sep).join("/"));
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 = path8.posix.join(skill.path, "SKILL.md");
1164
- const targetRel = path8.posix.join(".cursor", "skills", category, skill.id, "SKILL.md");
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(path8.sep).join("/");
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 path8.posix.join("registry/skills", rest);
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 path8.posix.join("registry/skills", "community", skillId, "SKILL.md");
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(path8.sep).join("/");
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 readFile4(
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(path8.sep).join("/");
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 readFile4(resolveContained(projectRoot, c.projectPath), "utf8");
1483
+ const content = await readFile5(resolveContained(projectRoot, c.projectPath), "utf8");
1282
1484
  const dest = resolveContained(registryRoot, c.registryPath);
1283
- await mkdir3(path8.dirname(dest), { recursive: true });
1284
- await writeFile2(dest, content, "utf8");
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 path10 from "path";
1413
- import { defineCommand as defineCommand4 } from "citty";
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 path9 from "path";
2020
+ import path11 from "path";
1419
2021
  import { fileURLToPath } from "url";
1420
- import { defineCommand as defineCommand3 } from "citty";
2022
+ import { defineCommand as defineCommand4 } from "citty";
1421
2023
  function bundledDashboardCandidates(filename, moduleUrl = import.meta.url) {
1422
- const here = path9.dirname(fileURLToPath(moduleUrl));
2024
+ const here = path11.dirname(fileURLToPath(moduleUrl));
1423
2025
  return [
1424
2026
  // Published / built: <pkg>/dist/index.js -> <pkg>/dashboard/
1425
- path9.join(here, "..", "dashboard", filename),
2027
+ path11.join(here, "..", "dashboard", filename),
1426
2028
  // Dev source: packages/cli/src/commands/*.ts -> packages/cli/dashboard/ (sync copy)
1427
- path9.join(here, "..", "..", "dashboard", filename),
2029
+ path11.join(here, "..", "..", "dashboard", filename),
1428
2030
  // From src/commands: packages/dashboard/ (unused layout); from dist: two levels above package
1429
- path9.join(here, "..", "..", "..", "dashboard", filename)
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 path9.resolve(candidate2);
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 = path9.resolve(cwd);
2045
+ let dir = path11.resolve(cwd);
1444
2046
  for (; ; ) {
1445
- const candidate2 = path9.join(dir, "dashboard", "start.mjs");
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 = path9.dirname(dir);
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 = path9.join(path9.resolve(base.trim()), "dashboard", "start.mjs");
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 = path9.join(path9.resolve(cwd), "..", "agent-kit", "dashboard", "start.mjs");
2067
+ const sibling = path11.join(path11.resolve(cwd), "..", "agent-kit", "dashboard", "start.mjs");
1466
2068
  try {
1467
2069
  await access2(sibling);
1468
- return path9.resolve(sibling);
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 = path9.resolve(cwd);
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 path9.resolve(top);
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: path9.dirname(path9.dirname(startPath)),
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 = defineCommand3({
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 = path10.resolve(cwd);
2137
+ let dir = path12.resolve(cwd);
1536
2138
  for (; ; ) {
1537
- const candidate2 = path10.join(dir, "dashboard", "start-broadcast.mjs");
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 = path10.dirname(dir);
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 = path10.join(path10.resolve(base.trim()), "dashboard", "start-broadcast.mjs");
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 = path10.join(
1558
- path10.resolve(cwd),
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 path10.resolve(sibling);
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 path10.resolve(candidate2);
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: path10.dirname(path10.dirname(startPath)),
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 = defineCommand4({
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 defineCommand5 } from "citty";
2229
+ import { defineCommand as defineCommand6 } from "citty";
1628
2230
 
1629
2231
  // src/lifecycle/diff.ts
1630
- import { readFile as readFile5 } from "fs/promises";
1631
- import path11 from "path";
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 readFile5(abs, "utf8");
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(path11.sep).join("/");
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(path11.sep).join("/");
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 = path11.posix.join(skill.path, "SKILL.md");
1698
- const targetRel = path11.posix.join(".cursor", "skills", category, skill.id, "SKILL.md");
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 = defineCommand5({
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 path22 from "path";
1767
- import { defineCommand as defineCommand6 } from "citty";
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 readFile6, stat } from "fs/promises";
1772
- import path12 from "path";
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 = path12.resolve(rootDir);
2402
+ const root = path14.resolve(rootDir);
1800
2403
  const candidates = [
1801
- path12.join(root, "node_modules", ".bin", "agent-kit"),
1802
- path12.join(root, "packages", "cli", "dist", "index.js")
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 = path12.resolve(rootDir);
2490
+ const root = path14.resolve(rootDir);
1823
2491
  const hooksJsonPath = ".cursor/hooks.json";
1824
- const hooksJsonAbs = path12.join(root, hooksJsonPath);
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 readFile6(hooksJsonAbs, "utf8"));
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 = path12.join(root, ".cursor", "hooks", "agent", "resolve-agent-kit.sh");
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 = path12.join(root, rel);
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 createHash2 } from "crypto";
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 createHash2("sha256").update(JSON.stringify(fingerprintInput)).digest("hex");
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 readFile9, writeFile as writeFile3 } from "fs/promises";
2160
- import path20 from "path";
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 readFile7 } from "fs/promises";
2164
- import path13 from "path";
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(path13.join(rootDir, relativePath)) ? { source: "file", value: `${relativePath}:${label}` } : void 0
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(path13.join(rootDir, "package.json"));
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 = path13.join(rootDir, manifestRelativePath);
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(path13.join(rootDir, ".cursor/plans")),
2252
- hasHandoff: await fileExists(path13.join(rootDir, ".cursor/HANDOFF.md")),
2253
- hasMemory: await fileExists(path13.join(rootDir, ".cursor/memory"))
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 = path13.join(rootDir, ".gitignore");
2939
+ const gitignorePath = path15.join(rootDir, ".gitignore");
2268
2940
  const hasGitignore = await fileExists(gitignorePath);
2269
- const gitignore = hasGitignore ? await readFile7(gitignorePath, "utf8") : "";
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(path13.join(rootDir, item))))).some(Boolean);
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(path13.join(rootDir, item)) ? readFile7(path13.join(rootDir, item), "utf8") : ""
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 path19 from "path";
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 path14 from "path";
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
- path14.join(rootDir, ".cursor", "agent-kit.config.json")
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(path14.join(rootDir, ".gitlab-ci.yml"))) {
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 path15 from "path";
3151
+ import path17 from "path";
2480
3152
  async function detectIde(rootDir) {
2481
- const hasCursor = await fileExists(path15.join(rootDir, ".cursor"));
2482
- const hasVSCode = await fileExists(path15.join(rootDir, ".vscode"));
2483
- const hasWindsurf = await fileExists(path15.join(rootDir, ".windsurfrules"));
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 path16 from "path";
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(path16.join(rootDir, "Dockerfile")) || await fileExists(path16.join(rootDir, "docker-compose.yml")) || await fileExists(path16.join(rootDir, "docker-compose.yaml"));
2521
- const kubernetes = await fileExists(path16.join(rootDir, "k8s")) || await fileExists(path16.join(rootDir, "kubernetes"));
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(path16.join(rootDir, filePath))) {
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(path16.join(rootDir, file)) ? file : void 0
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(path16.join(rootDir, file)) ? file : void 0
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 readFile8 } from "fs/promises";
2563
- import path17 from "path";
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
- path17.join(rootDir, ".cursor", "mcp.json"),
2568
- path17.join(rootDir, "mcp.json")
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 readFile8(configPath, "utf8");
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(path17.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
3256
+ if (await fileExists(path19.join(rootDir, ".github", "ISSUE_TEMPLATE"))) {
2585
3257
  tools.push("github-issues");
2586
3258
  }
2587
- if (await fileExists(path17.join(rootDir, ".github", "projects"))) {
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(path17.join(rootDir, "prisma/schema.prisma"));
2594
- const hasSequelize = await fileExists(path17.join(rootDir, "sequelize"));
2595
- const hasDrizzle = await fileExists(path17.join(rootDir, "drizzle.config.ts"));
2596
- const hasKnex = await fileExists(path17.join(rootDir, "knexfile.ts"));
2597
- const hasTypeorm = await fileExists(path17.join(rootDir, "ormconfig.json"));
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 path18 from "path";
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(path18.join(rootDir, lockfile))) {
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(path18.join(rootDir, item))))).some(Boolean);
2656
- const hasPackageJson = await fileExists(path18.join(rootDir, "package.json"));
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(path18.join(rootDir, "package.json")) ?? {};
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(path18.join(rootDir, "next.config.js")) || await fileExists(path18.join(rootDir, "next.config.mjs")) || await fileExists(path18.join(rootDir, "next.config.ts"));
2663
- const hasNestConfig = await fileExists(path18.join(rootDir, "nest-cli.json"));
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(path18.join(rootDir, "pnpm-workspace.yaml")),
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(path18.join(rootDir, "pyproject.toml"))) {
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(path18.join(rootDir, "go.mod"))) {
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(path18.join(rootDir, "Cargo.toml"))) {
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(path18.join(rootDir, "composer.json"))) {
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 = path19.resolve(rootDir);
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 = path20.join(beforeScan.rootDir, relativePath);
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 = path20.join(beforeScan.rootDir, gitignoreRelativePath);
2956
- const existingGitignore = await fileExists(gitignorePath) ? await readFile9(gitignorePath, "utf8") : "";
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 writeFile3(gitignorePath, mergedGitignore, "utf8");
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 = path20.join(beforeScan.rootDir, PROFILE_RELATIVE_PATH);
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 = path20.join(beforeScan.rootDir, CONTEXT_CONFIG_RELATIVE_PATH);
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 path21 from "path";
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 = path21.join(rootDir, READINESS_SNAPSHOT_RELATIVE_PATH);
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 = path22.resolve(cwd);
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 = defineCommand6({
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 defineCommand7 } from "citty";
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 shellInvocationHeads(command) {
3873
+ function shellSegments(command) {
3191
3874
  const normalized = normalizeShellCommand(command);
3192
3875
  if (!normalized) return [];
3193
- return normalized.split(/(?:&&|\|\||[;|])/).map((part) => part.trim().replace(/^(?:\w+=\S+\s+)*/, "")).filter(Boolean);
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
- if (t.startsWith("'") && t.endsWith("'") && t.length >= 2 || t.startsWith('"') && t.endsWith('"') && t.length >= 2) {
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) => shellInvocationHeads(cmd).some((head) => {
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 = defineCommand7({
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: defineCommand7({
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: defineCommand7({
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 readFile10, readdir as readdir2, writeFile as writeFile4 } from "fs/promises";
3386
- import path23 from "path";
3387
- import { defineCommand as defineCommand8 } from "citty";
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 readdir2(plansDir)).filter((f) => f.endsWith(".plan.md")).sort().reverse();
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 readFile10(path23.join(plansDir, file), "utf8");
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 readFile10(path23.join(plansDir, files[0]), "utf8") } : null;
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 = path23.join(rootDir, ".cursor", "agent-kit.config.json");
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 = defineCommand8({
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 = path23.join(args.cwd, ".cursor", "plans");
3519
- const handoffPath = path23.join(args.cwd, ".cursor", "HANDOFF.md");
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(path23.join(args.cwd, ".cursor"));
4250
+ await ensureDir(path25.join(args.cwd, ".cursor"));
3525
4251
  const content = buildHandoff(plan.file, fm, profile);
3526
- await writeFile4(handoffPath, content, "utf8");
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 = path23.join(args.cwd, "cursor-handoff");
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 path25 from "path";
3570
- import { defineCommand as defineCommand9 } from "citty";
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 readFile11 } from "fs/promises";
3584
- import path24 from "path";
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\` (### Unprocessed Files). Follow the ingest ritual there (detect \u2192 analyze \u2192 memory WRITE \u2192 triage). Do not auto-start analysis unless the user asks.`;
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 readFile11(filePath, "utf8")).split(/\r?\n/);
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 readFile11(filePath, "utf8");
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 = path24.join(root, ".cursor");
3694
- return await fileExists2(path24.join(cursor, "agent-kit.json")) || await fileExists2(path24.join(cursor, "commands", "agent-kit-onboard.md")) || await fileExists2(path24.join(cursor, "commands", "start-project.md"));
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 = path24.join(root, ".cursor", "context", "readiness.json");
4465
+ const snapshotPath = path26.join(root, ".cursor", "context", "readiness.json");
3734
4466
  let data;
3735
4467
  try {
3736
- data = JSON.parse(await readFile11(snapshotPath, "utf8"));
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 dogfoodDir = path24.join(root, "dogfood");
3767
- if (!await fileExists2(dogfoodDir)) return null;
3768
- const readme = path24.join(dogfoodDir, "README.md");
3769
- if (!await fileExists2(readme)) return null;
3770
- try {
3771
- const text = await readFile11(readme, "utf8");
3772
- if (!parseUnprocessedDogfoodItems(text).length) return null;
3773
- return DOGFOOD_INBOX_HINT;
3774
- } catch {
3775
- return null;
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 readFile11(path24.join(root, ".cursor", "context", "config.json"), "utf8")
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 = path24.resolve(rootDir);
3858
- const handoffPath = path24.join(root, ".cursor", "HANDOFF.md");
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 = defineCommand9({
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": defineCommand9({
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, path25.resolve(cwd));
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": defineCommand9({
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 defineCommand11 } from "citty";
4779
+ import { defineCommand as defineCommand12 } from "citty";
3942
4780
 
3943
4781
  // src/commands/install.ts
3944
- import path28 from "path";
3945
- import { defineCommand as defineCommand10 } from "citty";
4782
+ import path30 from "path";
4783
+ import { defineCommand as defineCommand11 } from "citty";
3946
4784
 
3947
4785
  // src/generator/personalization.ts
3948
- import { readFile as readFile12, writeFile as writeFile5 } from "fs/promises";
3949
- import path26 from "path";
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 = path26.join(rootDir, relativePath);
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(path26.dirname(target));
4111
- await writeFile5(target, content, "utf8");
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(path26.join(projectRoot, target)) ? target : null
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 = path26.posix.join(
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(path26.join(input.rootDir, target))) {
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(path26.join(input.rootDir, RESULT_PATH), result);
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 = path26.join(rootDir, ".cursor/agent-kit.config.json");
5067
+ const target = path28.join(rootDir, ".cursor/agent-kit.config.json");
4230
5068
  if (!await fileExists(target)) return null;
4231
- return JSON.parse(await readFile12(target, "utf8"));
5069
+ return JSON.parse(await readFile14(target, "utf8"));
4232
5070
  }
4233
5071
 
4234
5072
  // src/lifecycle/onboard-migration.ts
4235
- import { createHash as createHash3 } from "crypto";
4236
- import { readFile as readFile13, unlink } from "fs/promises";
4237
- import path27 from "path";
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 = path27.join(projectRoot, LEGACY_ONBOARD_PATH);
5082
+ const legacyPath = path29.join(projectRoot, LEGACY_ONBOARD_PATH);
4245
5083
  if (!await fileExists(legacyPath)) return "absent";
4246
- const namespacedPath = path27.join(projectRoot, NAMESPACED_ONBOARD_PATH);
5084
+ const namespacedPath = path29.join(projectRoot, NAMESPACED_ONBOARD_PATH);
4247
5085
  if (!await fileExists(namespacedPath)) return "preserved-customized";
4248
- const content = await readFile13(legacyPath);
4249
- const hash = createHash3("sha256").update(content).digest("hex");
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 = path28.resolve(options.cwd);
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 = defineCommand10({
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 = path28.resolve(args.cwd);
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 = defineCommand11({
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 path30 from "path";
4447
- import { defineCommand as defineCommand12 } from "citty";
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 readFile14, readdir as readdir3, stat as stat2 } from "fs/promises";
4452
- import path29 from "path";
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 readdir3(memoryDir);
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 = path29.basename(file);
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 = path29.resolve(rootDir);
4517
- const memoryDir = path29.join(root, ".cursor", "memory");
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 = path29.join(memoryDir, name);
5364
+ const abs = path31.join(memoryDir, name);
4523
5365
  try {
4524
- const [content, st] = await Promise.all([readFile14(abs, "utf8"), stat2(abs)]);
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 readFile14(path29.join(root, ".cursor", "HANDOFF.md"), "utf8");
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: path29.join(memoryDir, name),
4558
- relativePath: path29.relative(root, path29.join(memoryDir, name)).split(path29.sep).join("/"),
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 = defineCommand12({
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(path30.resolve(args.cwd));
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 path35 from "path";
4621
- import { defineCommand as defineCommand13 } from "citty";
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 mkdir4, readFile as readFile17, rm, unlink as unlink2 } from "fs/promises";
4706
- import path34 from "path";
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 path31 from "path";
4711
- var CANONICAL_REL = path31.join(".cursor", "scripts", "plan-external-review.sh");
4712
- var FALLBACK_REL = path31.join("scripts", "plan-external-review.sh");
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 = path31.join(root, CANONICAL_REL);
4735
- const fallbackPath = path31.join(root, FALLBACK_REL);
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 path32 from "path";
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
- path32.join(root, ".cursor", "context", "config.json")
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 = path32.join(root, "registry", "personas", "core", personaId, "persona.json");
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 readFile15, readdir as readdir4 } from "fs/promises";
4885
- import path33 from "path";
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 readdir4(plansDir)).filter((f) => f.endsWith(".plan.md")).sort();
4913
- return files[0] ? path33.join(plansDir, files[0]) : null;
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 readFile15(planPath, "utf8");
5758
+ return readFile17(planPath, "utf8");
4917
5759
  }
4918
5760
 
4919
5761
  // src/plan-loop/sentinel.ts
4920
- import { readFile as readFile16 } from "fs/promises";
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 readFile16(logPath, "utf8");
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 = path34.join(opts.root, ".cursor", "plans");
4991
- const stopFile = path34.join(opts.root, ".cursor", "loop.stop");
4992
- const logDir = path34.join(opts.root, ".cursor", "loop-logs");
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 mkdir4(logDir, { recursive: true });
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: ${path34.basename(planPath)}`);
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 = path34.join(logDir, `tick-${stamp()}.log`);
5057
- const relLog = path34.relative(opts.root, logPath);
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 readFile17(logPath, "utf8");
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 ${path34.relative(opts.root, logDir)}/`
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 = defineCommand13({
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: path35.resolve(args.cwd),
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 defineCommand14 } from "citty";
5217
- var scanCommand = defineCommand14({
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 path36 from "path";
5239
- import { defineCommand as defineCommand15 } from "citty";
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 = defineCommand15({
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 = path36.resolve(args.cwd);
6111
+ const rootDir = path38.resolve(args.cwd);
5270
6112
  const [manifest, rawProfile, scan] = await Promise.all([
5271
6113
  loadAgentKitManifest(rootDir),
5272
6114
  readJson(
5273
- path36.join(rootDir, ".cursor", "agent-kit.config.json")
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 defineCommand16 } from "citty";
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 path37 from "path";
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 intervalElapsed(lastCheckedAt, intervalDays) {
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 loadContextConfig(cwd) {
5423
- const configPath = path37.join(cwd, ".cursor", "context", "config.json");
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 = path37.join(cwd, ".cursor", "context", "config.json");
5428
- const existing = await loadContextConfig(cwd) ?? {};
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 loadContextConfig(cwd);
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 (!intervalElapsed(prefs.lastCheckedAt, prefs.intervalDays)) {
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 = defineCommand16({
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 readFile18 } from "fs/promises";
5652
- import path38 from "path";
5653
- import { defineCommand as defineCommand17 } from "citty";
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 = path38.resolve(cwd, explicit);
6554
+ const filePath2 = path40.resolve(cwd, explicit);
5700
6555
  try {
5701
- return { filePath: filePath2, content: await readFile18(filePath2, "utf8") };
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 = path38.isAbsolute(rel) ? rel : path38.resolve(cwd, rel);
6564
+ const filePath = path40.isAbsolute(rel) ? rel : path40.resolve(cwd, rel);
5710
6565
  try {
5711
- return { filePath, content: await readFile18(filePath, "utf8") };
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 = defineCommand17({
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: defineCommand17({
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 ? path38.resolve(cwd, fileArg) : path38.join(path38.resolve(cwd), ".cursor", "HANDOFF.md");
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 readFile18(filePath, "utf8");
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: defineCommand17({
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 = path38.resolve(cwd, fileArg);
6621
+ const filePath = path40.resolve(cwd, fileArg);
5767
6622
  let content = "";
5768
6623
  try {
5769
- content = await readFile18(filePath, "utf8");
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": defineCommand17({
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(path38.resolve(cwd));
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 = defineCommand18({
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,