mancode 0.6.3 → 0.6.4

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.
@@ -4,6 +4,7 @@ import {
4
4
  lstat,
5
5
  mkdir,
6
6
  readFile,
7
+ readlink,
7
8
  realpath,
8
9
  rename,
9
10
  rm,
@@ -289,17 +290,348 @@ function adapterManagedContentDigest(targetIdentity, managedContent) {
289
290
  return `sha256:${digest}`;
290
291
  }
291
292
  async function planV3AdapterFiles(projectRoot) {
293
+ return planPhysicalAdapterTargets(
294
+ path.resolve(projectRoot),
295
+ V3_ADAPTER_FILE_TARGETS,
296
+ V3_ADAPTER_PLATFORMS
297
+ );
298
+ }
299
+ async function planV3AdapterUpgradeFiles(projectRoot, platforms) {
292
300
  const root = path.resolve(projectRoot);
293
- const existing = /* @__PURE__ */ new Map();
294
- for (const target of V3_ADAPTER_FILE_TARGETS) {
295
- existing.set(target, await readAdapterTarget(root, target));
301
+ const selected = normalizeUpgradePlatforms(platforms);
302
+ const targets = /* @__PURE__ */ new Set();
303
+ for (const platform of selected) {
304
+ targets.add(primaryFileTarget(platform));
305
+ for (const mode of V3_MODE_NAMES) {
306
+ targets.add(modeEntryFileTarget(platform, mode));
307
+ }
308
+ for (const target of legacyAdapterTargetsForPlatform(platform, true)) {
309
+ targets.add(target);
310
+ }
311
+ }
312
+ return planPhysicalAdapterTargets(root, [...targets], selected);
313
+ }
314
+ async function planPhysicalAdapterTargets(root, targets, platforms) {
315
+ const ordered = orderAdapterTargets(targets);
316
+ const observations = await Promise.all(
317
+ ordered.map((target) => observeAdapterPhysicalTarget(root, target))
318
+ );
319
+ const groups = /* @__PURE__ */ new Map();
320
+ for (const observation of observations) {
321
+ const group = groups.get(observation.physicalKey) ?? [];
322
+ group.push(observation);
323
+ groups.set(observation.physicalKey, group);
324
+ }
325
+ const plans = [];
326
+ for (const group of groups.values()) {
327
+ const members = group.sort(
328
+ (left, right) => adapterTargetOrder(left.target) - adapterTargetOrder(right.target)
329
+ );
330
+ assertPhysicalGroupComposable(members);
331
+ const beforeContent = members[0]?.beforeContent ?? null;
332
+ if (members.some((member) => member.beforeContent !== beforeContent)) {
333
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
334
+ }
335
+ const targetContent = composePhysicalAdapterContent(
336
+ beforeContent,
337
+ members,
338
+ platforms
339
+ );
340
+ if (targetContent === null || targetContent === beforeContent) continue;
341
+ const representative = members.find((member) => member.target === "agents") ?? members[0];
342
+ if (representative === void 0) continue;
343
+ const links = uniqueLinkIdentities(
344
+ members.flatMap((member) => member.linkIdentities)
345
+ );
346
+ plans.push({
347
+ target: representative.target,
348
+ beforeContent,
349
+ targetContent,
350
+ ...links.length > 0 ? {
351
+ resolvedTarget: representative.physicalRelative,
352
+ linkIdentities: links
353
+ } : {}
354
+ });
355
+ }
356
+ return plans.sort(
357
+ (left, right) => adapterTargetOrder(left.target) - adapterTargetOrder(right.target)
358
+ );
359
+ }
360
+ function orderAdapterTargets(targets) {
361
+ return [...new Set(targets)].sort(
362
+ (left, right) => adapterTargetOrder(left) - adapterTargetOrder(right)
363
+ );
364
+ }
365
+ function adapterTargetOrder(target) {
366
+ const index = V3_ADAPTER_FILE_TARGETS.indexOf(target);
367
+ return index < 0 ? Number.MAX_SAFE_INTEGER : index;
368
+ }
369
+ async function observeAdapterPhysicalTarget(root, target) {
370
+ const logicalPath = v3AdapterTargetPath(root, target);
371
+ const entry = await lstatOrNullAdapter(logicalPath);
372
+ if (entry === null) {
373
+ await assertAdapterPathSafe(root, logicalPath);
374
+ return {
375
+ target,
376
+ logicalPath,
377
+ physicalRelative: relativeAdapterPath(root, logicalPath),
378
+ physicalKey: `path:${relativeAdapterPath(root, logicalPath)}`,
379
+ beforeContent: null,
380
+ linkIdentities: []
381
+ };
382
+ }
383
+ if (entry.isSymbolicLink()) {
384
+ const resolved2 = await resolveObservedAdapterSymlinkChain(
385
+ root,
386
+ logicalPath
387
+ );
388
+ const physicalRelative2 = normalizeAdapterRelativePath(resolved2.relative);
389
+ return {
390
+ target,
391
+ logicalPath,
392
+ physicalRelative: physicalRelative2,
393
+ physicalKey: physicalFileKey(resolved2.entry),
394
+ beforeContent: await readFile(resolved2.path, "utf8"),
395
+ linkIdentities: resolved2.linkIdentities
396
+ };
397
+ }
398
+ if (!entry.isFile()) {
399
+ throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
400
+ }
401
+ const resolved = await realpath(logicalPath);
402
+ const resolvedRelative = await relativeWithinRealRoot(root, resolved);
403
+ if (resolvedRelative === null) {
404
+ throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
405
+ }
406
+ const resolvedEntry = await lstat(resolved);
407
+ if (!resolvedEntry.isFile() || resolvedEntry.isSymbolicLink()) {
408
+ throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
409
+ }
410
+ const physicalRelative = normalizeAdapterRelativePath(resolvedRelative);
411
+ return {
412
+ target,
413
+ logicalPath,
414
+ physicalRelative,
415
+ physicalKey: physicalFileKey(resolvedEntry),
416
+ beforeContent: await readFile(resolved, "utf8"),
417
+ linkIdentities: []
418
+ };
419
+ }
420
+ async function resolveObservedAdapterSymlinkChain(root, logicalPath) {
421
+ let current = path.resolve(logicalPath);
422
+ const seen = /* @__PURE__ */ new Set();
423
+ const linkIdentities = [];
424
+ for (let depth = 0; depth < 40; depth += 1) {
425
+ if (!isPathInsideRoot(root, current) || seen.has(current)) {
426
+ throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
427
+ }
428
+ seen.add(current);
429
+ const entry = await lstatOrNullAdapter(current);
430
+ if (entry === null) throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
431
+ if (!entry.isSymbolicLink()) {
432
+ if (!entry.isFile()) throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
433
+ const resolved = await realpath(current);
434
+ const relative = await relativeWithinRealRoot(root, resolved);
435
+ if (relative === null || entry.nlink > 1) {
436
+ throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
437
+ }
438
+ return { path: resolved, relative, entry, linkIdentities };
439
+ }
440
+ const linkTarget = await readlink(current, "utf8");
441
+ linkIdentities.push({
442
+ linkPath: relativeAdapterPath(root, current),
443
+ linkTarget
444
+ });
445
+ current = path.resolve(path.dirname(current), linkTarget);
446
+ }
447
+ throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
448
+ }
449
+ function physicalFileKey(entry) {
450
+ if (entry.nlink > 1) {
451
+ throw new Error("MANCODE_V3_ADAPTER_HARDLINK_UNSAFE");
452
+ }
453
+ return `inode:${entry.dev}:${entry.ino}`;
454
+ }
455
+ function uniqueLinkIdentities(identities) {
456
+ const byPath = /* @__PURE__ */ new Map();
457
+ for (const identity of identities) {
458
+ const previous = byPath.get(identity.linkPath);
459
+ if (previous !== void 0 && previous.linkTarget !== identity.linkTarget) {
460
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
461
+ }
462
+ byPath.set(identity.linkPath, identity);
463
+ }
464
+ return [...byPath.values()].sort(
465
+ (left, right) => compareUtf8(left.linkPath, right.linkPath)
466
+ );
467
+ }
468
+ function isPathInsideRoot(root, candidate) {
469
+ const relative = path.relative(root, candidate);
470
+ return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
471
+ }
472
+ function normalizeAdapterRelativePath(value) {
473
+ return value.split(path.sep).join("/");
474
+ }
475
+ function compareUtf8(left, right) {
476
+ return Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"));
477
+ }
478
+ async function lstatOrNullAdapter(target) {
479
+ try {
480
+ return await lstat(target);
481
+ } catch (error) {
482
+ if (isNodeError(error) && error.code === "ENOENT") return null;
483
+ throw error;
484
+ }
485
+ }
486
+ function assertPhysicalGroupComposable(group) {
487
+ if (group.length <= 1) return;
488
+ if (group.every((member) => isComposableEmbeddedAdapterTarget(member.target))) {
489
+ return;
296
490
  }
491
+ throw new Error("MANCODE_V3_ADAPTER_SHARED_TARGET_CONFLICT");
492
+ }
493
+ function composePhysicalAdapterContent(beforeContent, members, platforms) {
494
+ const memberTargets = new Set(members.map((member) => member.target));
495
+ let content = beforeContent;
496
+ for (const platform of platforms) {
497
+ const primary = primaryFileTarget(platform);
498
+ if (!memberTargets.has(primary)) continue;
499
+ content = applyPrimaryBootstrapContent(content, platform);
500
+ }
501
+ for (const member of members) {
502
+ if (isPrimaryAdapterTarget(member.target)) continue;
503
+ content = desiredAdapterTargetContent(member.target, content);
504
+ }
505
+ return content;
506
+ }
507
+ function isPrimaryAdapterTarget(target) {
508
+ return target === "claude-skill" || target === "cursor-rule" || target === "agents" || target === "copilot-instructions";
509
+ }
510
+ function applyPrimaryBootstrapContent(current, platform) {
511
+ switch (platform) {
512
+ case "claude-code":
513
+ return replaceManagedV3BlockText(
514
+ current ?? "",
515
+ CONTINUITY_CLAUDE_START_MARKER,
516
+ CONTINUITY_CLAUDE_END_MARKER,
517
+ renderV3Bootstrap(platform)
518
+ );
519
+ case "cursor":
520
+ return managedFilePlan(
521
+ "cursor-rule",
522
+ current,
523
+ renderCursorRule(renderV3Bootstrap(platform))
524
+ ).targetContent;
525
+ case "codex":
526
+ return replaceManagedV3BlockText(
527
+ removeLegacyV3Block(
528
+ removeLegacyAgentsBlocks(current ?? ""),
529
+ LEGACY_V3_CODEX_MARKERS
530
+ ),
531
+ V3_CODEX_START_MARKER,
532
+ V3_CODEX_END_MARKER,
533
+ renderV3Bootstrap(platform)
534
+ );
535
+ case "zcode":
536
+ return replaceManagedV3BlockText(
537
+ removeLegacyV3Block(
538
+ removeLegacyAgentsBlocks(current ?? ""),
539
+ LEGACY_V3_ZCODE_MARKERS
540
+ ),
541
+ V3_ZCODE_START_MARKER,
542
+ V3_ZCODE_END_MARKER,
543
+ renderV3Bootstrap(platform)
544
+ );
545
+ case "kimi-code":
546
+ return replaceManagedV3BlockText(
547
+ removeLegacyV3Block(
548
+ removeLegacyAgentsBlocks(current ?? ""),
549
+ LEGACY_KIMI_MARKERS
550
+ ),
551
+ V3_KIMI_START_MARKER,
552
+ V3_KIMI_END_MARKER,
553
+ renderV3Bootstrap(platform)
554
+ );
555
+ case "qoder":
556
+ return replaceManagedV3BlockText(
557
+ removeLegacyV3Block(current ?? "", LEGACY_QODER_MARKERS),
558
+ V3_QODER_START_MARKER,
559
+ V3_QODER_END_MARKER,
560
+ renderV3Bootstrap(platform)
561
+ );
562
+ case "dsh":
563
+ return replaceManagedV3BlockText(
564
+ removeLegacyV3Block(current ?? "", LEGACY_DSH_MARKERS),
565
+ V3_DSH_START_MARKER,
566
+ V3_DSH_END_MARKER,
567
+ renderV3Bootstrap(platform)
568
+ );
569
+ case "copilot":
570
+ return replaceManagedV3BlockText(
571
+ removeLegacyV3Block(
572
+ removeManagedBlock(current ?? ""),
573
+ LEGACY_V3_COPILOT_MARKERS
574
+ ),
575
+ V3_COPILOT_START_MARKER,
576
+ V3_COPILOT_END_MARKER,
577
+ renderV3Bootstrap(platform)
578
+ );
579
+ }
580
+ }
581
+ function isComposableEmbeddedAdapterTarget(target) {
582
+ return target === "claude-skill" || target === "agents" || target === "copilot-instructions";
583
+ }
584
+ function desiredAdapterTargetContent(target, current) {
585
+ switch (target) {
586
+ case "claude-skill":
587
+ return replaceManagedV3BlockText(
588
+ current ?? "",
589
+ CONTINUITY_CLAUDE_START_MARKER,
590
+ CONTINUITY_CLAUDE_END_MARKER,
591
+ renderV3Bootstrap("claude-code")
592
+ );
593
+ case "cursor-rule":
594
+ return managedFilePlan(
595
+ target,
596
+ current,
597
+ renderCursorRule(renderV3Bootstrap("cursor"))
598
+ ).targetContent;
599
+ case "agents":
600
+ return renderAgentsBootstrapTarget(current ?? "");
601
+ case "copilot-instructions":
602
+ return replaceManagedV3BlockText(
603
+ removeLegacyV3Block(
604
+ removeManagedBlock(current ?? ""),
605
+ LEGACY_V3_COPILOT_MARKERS
606
+ ),
607
+ V3_COPILOT_START_MARKER,
608
+ V3_COPILOT_END_MARKER,
609
+ renderV3Bootstrap("copilot")
610
+ );
611
+ default: {
612
+ const modeTarget = parseModeEntryFileTarget(target);
613
+ if (modeTarget !== null) {
614
+ const modeFileTarget = target;
615
+ return managedModeEntryPlan(
616
+ modeFileTarget,
617
+ current,
618
+ renderModeEntryForFileTarget(modeFileTarget)
619
+ ).targetContent;
620
+ }
621
+ const retirement = planLegacyAdapterRetirement(
622
+ /* @__PURE__ */ new Map([[target, current]])
623
+ ).find((plan) => plan.target === target);
624
+ return retirement?.targetContent ?? current;
625
+ }
626
+ }
627
+ }
628
+ function renderAgentsBootstrapTarget(existing) {
297
629
  const agents = removeLegacyV3Block(
298
630
  removeLegacyV3Block(
299
631
  removeLegacyV3Block(
300
632
  removeLegacyV3Block(
301
633
  removeLegacyV3Block(
302
- removeLegacyAgentsBlocks(existing.get("agents") ?? ""),
634
+ removeLegacyAgentsBlocks(existing),
303
635
  LEGACY_V3_CODEX_MARKERS
304
636
  ),
305
637
  LEGACY_V3_ZCODE_MARKERS
@@ -310,7 +642,7 @@ async function planV3AdapterFiles(projectRoot) {
310
642
  ),
311
643
  LEGACY_DSH_MARKERS
312
644
  );
313
- const nextAgents = replaceManagedV3BlockText(
645
+ return replaceManagedV3BlockText(
314
646
  replaceManagedV3BlockText(
315
647
  replaceManagedV3BlockText(
316
648
  replaceManagedV3BlockText(
@@ -336,125 +668,6 @@ async function planV3AdapterFiles(projectRoot) {
336
668
  V3_DSH_END_MARKER,
337
669
  renderV3Bootstrap("dsh")
338
670
  );
339
- const legacyAdapterPlans = planLegacyAdapterRetirement(existing);
340
- const plans = [
341
- {
342
- target: "claude-skill",
343
- beforeContent: existing.get("claude-skill") ?? null,
344
- targetContent: replaceManagedV3BlockText(
345
- existing.get("claude-skill") ?? "",
346
- CONTINUITY_CLAUDE_START_MARKER,
347
- CONTINUITY_CLAUDE_END_MARKER,
348
- renderV3Bootstrap("claude-code")
349
- )
350
- },
351
- managedFilePlan(
352
- "cursor-rule",
353
- existing.get("cursor-rule") ?? null,
354
- renderCursorRule(renderV3Bootstrap("cursor"))
355
- ),
356
- {
357
- target: "agents",
358
- beforeContent: existing.get("agents") ?? null,
359
- targetContent: nextAgents
360
- },
361
- {
362
- target: "copilot-instructions",
363
- beforeContent: existing.get("copilot-instructions") ?? null,
364
- targetContent: replaceManagedV3BlockText(
365
- removeLegacyV3Block(
366
- removeManagedBlock(existing.get("copilot-instructions") ?? ""),
367
- LEGACY_V3_COPILOT_MARKERS
368
- ),
369
- V3_COPILOT_START_MARKER,
370
- V3_COPILOT_END_MARKER,
371
- renderV3Bootstrap("copilot")
372
- )
373
- },
374
- ...V3_MODE_ENTRY_FILE_TARGETS.map(
375
- (target) => managedModeEntryPlan(
376
- target,
377
- existing.get(target) ?? null,
378
- renderModeEntryForFileTarget(target)
379
- )
380
- ),
381
- ...legacyAdapterPlans
382
- ];
383
- return annotateWriteThroughPlans(root, plans);
384
- }
385
- async function annotateWriteThroughPlans(root, plans) {
386
- return Promise.all(
387
- plans.map(async (plan) => {
388
- const resolved = await writeThroughResolvedPath(
389
- root,
390
- v3AdapterTargetPath(root, plan.target)
391
- );
392
- if (resolved === null) return plan;
393
- const relative = await relativeWithinRealRoot(root, resolved);
394
- if (relative === null) return plan;
395
- return { ...plan, resolvedTarget: relative };
396
- })
397
- );
398
- }
399
- async function effectivePrimaryTarget(root, platform) {
400
- const primary = primaryFileTarget(platform);
401
- const resolved = await writeThroughResolvedPath(
402
- root,
403
- v3AdapterTargetPath(root, primary)
404
- );
405
- if (resolved === null) return primary;
406
- const agentsPath = v3AdapterTargetPath(root, "agents");
407
- const realAgents = await resolveAdapterSymlink(agentsPath);
408
- if (realAgents !== null && path.resolve(resolved) === path.resolve(realAgents)) {
409
- return "agents";
410
- }
411
- return primary;
412
- }
413
- async function planV3AdapterUpgradeFiles(projectRoot, platforms) {
414
- const root = path.resolve(projectRoot);
415
- const selected = normalizeUpgradePlatforms(platforms);
416
- const targetSet = /* @__PURE__ */ new Set();
417
- const effectivePrimary = /* @__PURE__ */ new Map();
418
- for (const platform of selected) {
419
- const primary = await effectivePrimaryTarget(root, platform);
420
- effectivePrimary.set(platform, primary);
421
- targetSet.add(primary);
422
- for (const mode of V3_MODE_NAMES) {
423
- targetSet.add(modeEntryFileTarget(platform, mode));
424
- }
425
- for (const target of legacyAdapterTargetsForPlatform(platform, true)) {
426
- targetSet.add(target);
427
- }
428
- }
429
- const existing = /* @__PURE__ */ new Map();
430
- for (const target of targetSet) {
431
- existing.set(target, await readAdapterTarget(root, target));
432
- }
433
- const desired = new Map(existing);
434
- for (const platform of selected) {
435
- planPlatformBootstrapUpgrade(
436
- desired,
437
- platform,
438
- effectivePrimary.get(platform)
439
- );
440
- for (const mode of V3_MODE_NAMES) {
441
- const target = modeEntryFileTarget(platform, mode);
442
- const current = desired.get(target) ?? null;
443
- desired.set(
444
- target,
445
- managedModeEntryPlan(target, current, renderV3ModeEntry(mode, platform)).targetContent
446
- );
447
- }
448
- }
449
- const plans = [...desired.entries()].filter(([target, targetContent]) => existing.get(target) !== targetContent).map(([target, targetContent]) => ({
450
- target,
451
- beforeContent: existing.get(target) ?? null,
452
- targetContent: targetContent ?? ""
453
- }));
454
- const legacyPlans = planLegacyAdapterRetirement(existing).filter(
455
- (legacyPlan) => !plans.some((candidate) => candidate.target === legacyPlan.target)
456
- );
457
- return annotateWriteThroughPlans(root, [...plans, ...legacyPlans]);
458
671
  }
459
672
  async function stageV3AdapterUpgradeFiles(projectRoot, operationId, plans) {
460
673
  if (!/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/.test(operationId)) {
@@ -493,18 +706,7 @@ async function applyV3AdapterFilePlan(projectRoot, plan) {
493
706
  throw new Error("MANCODE_V3_ADAPTER_TARGET_INVALID");
494
707
  }
495
708
  const target = v3AdapterTargetPath(root, plan.target);
496
- const writePath = plan.resolvedTarget === void 0 ? target : path.join(root, plan.resolvedTarget);
497
- if (plan.resolvedTarget !== void 0) {
498
- const entry = await lstat(target).catch(() => null);
499
- const resolved = entry?.isSymbolicLink() === true ? await resolveAdapterSymlink(target) : null;
500
- const realWrite = await resolveAdapterSymlink(writePath);
501
- if (resolved === null || realWrite === null || resolved !== realWrite) {
502
- throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
503
- }
504
- await assertAdapterPathSafe(root, writePath);
505
- } else {
506
- await assertAdapterPathSafe(root, target);
507
- }
709
+ const writePath = await resolvePlannedAdapterWritePath(root, target, plan);
508
710
  const retiredBootstrapPlatform = retiredBootstrapPlatformFor(plan.target);
509
711
  if (retiredBootstrapPlatform !== null) {
510
712
  for (const retired of retiredBootstrapSpecs(
@@ -514,7 +716,10 @@ async function applyV3AdapterFilePlan(projectRoot, plan) {
514
716
  await assertAdapterPathSafe(root, retired.filePath);
515
717
  }
516
718
  }
517
- const current = await readAdapterTarget(root, plan.target);
719
+ const current = await readFile(writePath, "utf8").catch((error) => {
720
+ if (isNodeError(error) && error.code === "ENOENT") return null;
721
+ throw error;
722
+ });
518
723
  if (current === plan.targetContent) {
519
724
  if (retiredBootstrapPlatform !== null) {
520
725
  await removeRetiredBootstrapFiles(root, retiredBootstrapPlatform);
@@ -524,12 +729,123 @@ async function applyV3AdapterFilePlan(projectRoot, plan) {
524
729
  if (current !== plan.beforeContent) {
525
730
  throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
526
731
  }
732
+ const verifiedWritePath = await resolvePlannedAdapterWritePath(
733
+ root,
734
+ target,
735
+ plan
736
+ );
737
+ if (!await sameResolvedPath(verifiedWritePath, writePath)) {
738
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
739
+ }
527
740
  await mkdir(path.dirname(writePath), { recursive: true });
528
741
  await atomicWrite(writePath, plan.targetContent);
529
742
  if (retiredBootstrapPlatform !== null) {
530
743
  await removeRetiredBootstrapFiles(root, retiredBootstrapPlatform);
531
744
  }
532
745
  }
746
+ async function resolvePlannedAdapterWritePath(root, logicalTarget, plan) {
747
+ await assertAdapterPathSafe(root, logicalTarget);
748
+ if (plan.resolvedTarget === void 0) {
749
+ if (plan.linkIdentities !== void 0 && plan.linkIdentities.length > 0) {
750
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
751
+ }
752
+ const entry = await lstatOrNullAdapter(logicalTarget);
753
+ if (entry?.isSymbolicLink()) {
754
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
755
+ }
756
+ if (entry !== null && (!entry.isFile() || entry.nlink > 1)) {
757
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
758
+ }
759
+ return logicalTarget;
760
+ }
761
+ if (plan.linkIdentities !== void 0 && plan.linkIdentities.length === 0) {
762
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
763
+ }
764
+ const resolvedTarget = safePlannedRelativePath(root, plan.resolvedTarget);
765
+ await assertAdapterPathSafe(root, resolvedTarget);
766
+ const resolvedEntry = await lstatOrNullAdapter(resolvedTarget);
767
+ if (resolvedEntry === null || !resolvedEntry.isFile() || resolvedEntry.isSymbolicLink() || resolvedEntry.nlink > 1) {
768
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
769
+ }
770
+ if (plan.linkIdentities === void 0) {
771
+ const logicalEntry2 = await lstatOrNullAdapter(logicalTarget);
772
+ if (!logicalEntry2?.isSymbolicLink() || !await sameResolvedPath(logicalTarget, resolvedTarget)) {
773
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
774
+ }
775
+ return resolvedTarget;
776
+ }
777
+ await assertLinkIdentities(root, plan.linkIdentities, resolvedTarget);
778
+ const logicalEntry = await lstatOrNullAdapter(logicalTarget);
779
+ if (logicalEntry === null)
780
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
781
+ if (logicalEntry.isSymbolicLink()) {
782
+ const logicalRelative = relativeAdapterPath(root, logicalTarget);
783
+ if (!plan.linkIdentities?.some(
784
+ (identity) => identity.linkPath === logicalRelative
785
+ )) {
786
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
787
+ }
788
+ } else if (!logicalEntry.isFile()) {
789
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
790
+ } else if (!await sameResolvedPath(logicalTarget, resolvedTarget)) {
791
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
792
+ }
793
+ return resolvedTarget;
794
+ }
795
+ function safePlannedRelativePath(root, relative) {
796
+ if (typeof relative !== "string" || !relative.trim() || path.isAbsolute(relative) || relative.split(/[\\/]/u).some((segment) => segment === ".." || !segment)) {
797
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
798
+ }
799
+ const resolved = path.resolve(root, relative);
800
+ if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`)) {
801
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
802
+ }
803
+ return resolved;
804
+ }
805
+ async function readV3AdapterFilePlanContent(projectRoot, plan) {
806
+ const root = path.resolve(projectRoot);
807
+ const logicalTarget = v3AdapterTargetPath(root, plan.target);
808
+ const readPath = await resolvePlannedAdapterWritePath(
809
+ root,
810
+ logicalTarget,
811
+ plan
812
+ );
813
+ try {
814
+ const entry = await lstat(readPath);
815
+ if (!entry.isFile() || entry.isSymbolicLink() || entry.nlink > 1) {
816
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
817
+ }
818
+ return await readFile(readPath, "utf8");
819
+ } catch (error) {
820
+ if (isNodeError(error) && error.code === "ENOENT") return null;
821
+ throw error;
822
+ }
823
+ }
824
+ async function assertLinkIdentities(root, identities, resolvedTarget) {
825
+ for (const identity of identities) {
826
+ const linkPath = safePlannedRelativePath(root, identity.linkPath);
827
+ await assertAdapterPathSafe(root, linkPath);
828
+ const entry = await lstatOrNullAdapter(linkPath);
829
+ if (entry === null || !entry.isSymbolicLink()) {
830
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
831
+ }
832
+ const linkTarget = await readlink(linkPath, "utf8");
833
+ if (linkTarget !== identity.linkTarget) {
834
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
835
+ }
836
+ const actual = await resolveAdapterSymlink(linkPath);
837
+ if (actual === null || !await sameResolvedPath(actual, resolvedTarget)) {
838
+ throw new Error("MANCODE_V3_ADAPTER_TARGET_CONFLICT");
839
+ }
840
+ }
841
+ }
842
+ async function sameResolvedPath(left, right) {
843
+ const [leftReal, rightReal] = await Promise.all([
844
+ realpath(left).catch(() => path.resolve(left)),
845
+ realpath(right).catch(() => path.resolve(right))
846
+ ]);
847
+ return path.resolve(leftReal) === path.resolve(rightReal);
848
+ }
533
849
  async function stageV3Adapter(projectRoot, platform) {
534
850
  const root = path.resolve(projectRoot);
535
851
  await assertPlatformAdapterPathsSafe(root, platform);
@@ -1248,110 +1564,6 @@ function primaryFileTarget(platform) {
1248
1564
  return "copilot-instructions";
1249
1565
  }
1250
1566
  }
1251
- function planPlatformBootstrapUpgrade(desired, platform, targetOverride) {
1252
- const target = targetOverride ?? primaryFileTarget(platform);
1253
- const current = desired.get(target) ?? null;
1254
- switch (platform) {
1255
- case "claude-code":
1256
- desired.set(
1257
- target,
1258
- replaceManagedV3BlockText(
1259
- current ?? "",
1260
- CONTINUITY_CLAUDE_START_MARKER,
1261
- CONTINUITY_CLAUDE_END_MARKER,
1262
- renderV3Bootstrap(platform)
1263
- )
1264
- );
1265
- return;
1266
- case "cursor":
1267
- desired.set(
1268
- target,
1269
- managedFilePlan(
1270
- target,
1271
- current,
1272
- renderCursorRule(renderV3Bootstrap(platform))
1273
- ).targetContent
1274
- );
1275
- return;
1276
- case "codex":
1277
- desired.set(
1278
- target,
1279
- replaceManagedV3BlockText(
1280
- removeLegacyV3Block(
1281
- removeLegacyAgentsBlocks(current ?? ""),
1282
- LEGACY_V3_CODEX_MARKERS
1283
- ),
1284
- V3_CODEX_START_MARKER,
1285
- V3_CODEX_END_MARKER,
1286
- renderV3Bootstrap(platform)
1287
- )
1288
- );
1289
- return;
1290
- case "zcode":
1291
- desired.set(
1292
- target,
1293
- replaceManagedV3BlockText(
1294
- removeLegacyV3Block(
1295
- removeLegacyAgentsBlocks(current ?? ""),
1296
- LEGACY_V3_ZCODE_MARKERS
1297
- ),
1298
- V3_ZCODE_START_MARKER,
1299
- V3_ZCODE_END_MARKER,
1300
- renderV3Bootstrap(platform)
1301
- )
1302
- );
1303
- return;
1304
- case "kimi-code":
1305
- desired.set(
1306
- target,
1307
- replaceManagedV3BlockText(
1308
- removeLegacyV3Block(
1309
- removeLegacyAgentsBlocks(current ?? ""),
1310
- LEGACY_KIMI_MARKERS
1311
- ),
1312
- V3_KIMI_START_MARKER,
1313
- V3_KIMI_END_MARKER,
1314
- renderV3Bootstrap(platform)
1315
- )
1316
- );
1317
- return;
1318
- case "qoder":
1319
- desired.set(
1320
- target,
1321
- replaceManagedV3BlockText(
1322
- removeLegacyV3Block(current ?? "", LEGACY_QODER_MARKERS),
1323
- V3_QODER_START_MARKER,
1324
- V3_QODER_END_MARKER,
1325
- renderV3Bootstrap(platform)
1326
- )
1327
- );
1328
- return;
1329
- case "dsh":
1330
- desired.set(
1331
- target,
1332
- replaceManagedV3BlockText(
1333
- removeLegacyV3Block(current ?? "", LEGACY_DSH_MARKERS),
1334
- V3_DSH_START_MARKER,
1335
- V3_DSH_END_MARKER,
1336
- renderV3Bootstrap(platform)
1337
- )
1338
- );
1339
- return;
1340
- case "copilot":
1341
- desired.set(
1342
- target,
1343
- replaceManagedV3BlockText(
1344
- removeLegacyV3Block(
1345
- removeManagedBlock(current ?? ""),
1346
- LEGACY_V3_COPILOT_MARKERS
1347
- ),
1348
- V3_COPILOT_START_MARKER,
1349
- V3_COPILOT_END_MARKER,
1350
- renderV3Bootstrap(platform)
1351
- )
1352
- );
1353
- }
1354
- }
1355
1567
  function primaryManagedTargetSpec(platform) {
1356
1568
  const target = targetFor(platform);
1357
1569
  switch (platform) {
@@ -2014,8 +2226,13 @@ async function replaceUnsafeV3AdapterSymlinks(entries) {
2014
2226
  if (entry.kind !== "symlink" || !entry.finalTarget || entry.resolvedTo === null) {
2015
2227
  continue;
2016
2228
  }
2229
+ const projectRoot = entry.projectRoot;
2230
+ if (projectRoot === void 0 || await relativeWithinRealRoot(projectRoot, entry.resolvedTo) === null) {
2231
+ continue;
2232
+ }
2017
2233
  const resolvedEntry = await lstat(entry.resolvedTo).catch(() => null);
2018
- if (resolvedEntry === null || !resolvedEntry.isFile()) continue;
2234
+ if (resolvedEntry === null || !resolvedEntry.isFile() || resolvedEntry.isSymbolicLink())
2235
+ continue;
2019
2236
  const content = await readFile(entry.resolvedTo);
2020
2237
  await rm(entry.target, { force: true });
2021
2238
  await writeFile(entry.target, content);
@@ -2056,7 +2273,8 @@ async function findUnsafeAdapterPathEntry(root, target) {
2056
2273
  relative,
2057
2274
  kind: "outside-root",
2058
2275
  finalTarget: false,
2059
- resolvedTo: null
2276
+ resolvedTo: null,
2277
+ projectRoot: root
2060
2278
  };
2061
2279
  }
2062
2280
  const rootEntry = await lstat(root);
@@ -2066,7 +2284,8 @@ async function findUnsafeAdapterPathEntry(root, target) {
2066
2284
  relative,
2067
2285
  kind: "root-symlink",
2068
2286
  finalTarget: false,
2069
- resolvedTo: null
2287
+ resolvedTo: null,
2288
+ projectRoot: root
2070
2289
  };
2071
2290
  }
2072
2291
  const segments = relative.split(path.sep);
@@ -2081,7 +2300,8 @@ async function findUnsafeAdapterPathEntry(root, target) {
2081
2300
  relative,
2082
2301
  kind: "symlink",
2083
2302
  finalTarget: index === segments.length - 1,
2084
- resolvedTo: await resolveAdapterSymlink(current)
2303
+ resolvedTo: await resolveAdapterSymlinkForInspection(current),
2304
+ projectRoot: root
2085
2305
  };
2086
2306
  }
2087
2307
  if (index < segments.length - 1 && !entry.isDirectory()) {
@@ -2090,7 +2310,8 @@ async function findUnsafeAdapterPathEntry(root, target) {
2090
2310
  relative,
2091
2311
  kind: "not-directory",
2092
2312
  finalTarget: false,
2093
- resolvedTo: null
2313
+ resolvedTo: null,
2314
+ projectRoot: root
2094
2315
  };
2095
2316
  }
2096
2317
  } catch (error) {
@@ -2107,6 +2328,29 @@ async function resolveAdapterSymlink(linkPath) {
2107
2328
  return null;
2108
2329
  }
2109
2330
  }
2331
+ async function resolveAdapterSymlinkForInspection(linkPath) {
2332
+ let current = path.resolve(linkPath);
2333
+ const seen = /* @__PURE__ */ new Set();
2334
+ for (let depth = 0; depth < 40; depth += 1) {
2335
+ if (seen.has(current)) return null;
2336
+ seen.add(current);
2337
+ const entry = await lstatOrNullAdapter(current);
2338
+ if (entry === null) return current;
2339
+ if (!entry.isSymbolicLink()) {
2340
+ return await realpath(current).catch(() => null) ?? current;
2341
+ }
2342
+ let target;
2343
+ try {
2344
+ target = await readlink(current, "utf8");
2345
+ } catch {
2346
+ return null;
2347
+ }
2348
+ current = path.resolve(
2349
+ path.isAbsolute(target) ? target : path.join(path.dirname(current), target)
2350
+ );
2351
+ }
2352
+ return null;
2353
+ }
2110
2354
  async function writeThroughResolvedPath(root, target) {
2111
2355
  const entry = await lstat(target).catch((error) => {
2112
2356
  if (isNodeError(error) && error.code === "ENOENT") return null;
@@ -2303,14 +2547,14 @@ async function delay(milliseconds) {
2303
2547
  }
2304
2548
 
2305
2549
  export {
2306
- ACCEPTED_STATE_NARRATIVE_GUIDANCE,
2307
- INTERFACE_EMOJI_ICON_GUIDANCE,
2308
- VISUAL_DIRECTION_SELECTION_GUIDANCE,
2309
2550
  DEFAULT_MANCODE_START_MARKER,
2310
2551
  DEFAULT_MANCODE_END_MARKER,
2311
2552
  removeManagedBlock,
2312
2553
  hasManagedBlock,
2313
2554
  replaceManagedBlock,
2555
+ ACCEPTED_STATE_NARRATIVE_GUIDANCE,
2556
+ INTERFACE_EMOJI_ICON_GUIDANCE,
2557
+ VISUAL_DIRECTION_SELECTION_GUIDANCE,
2314
2558
  V3_ADAPTER_VERSION,
2315
2559
  V3_ADAPTER_MANAGED_MARKER,
2316
2560
  V3_MODE_ENTRY_MANAGED_MARKER,
@@ -2323,6 +2567,7 @@ export {
2323
2567
  planV3AdapterUpgradeFiles,
2324
2568
  stageV3AdapterUpgradeFiles,
2325
2569
  applyV3AdapterFilePlan,
2570
+ readV3AdapterFilePlanContent,
2326
2571
  stageV3Adapter,
2327
2572
  v3AdapterTargetPath,
2328
2573
  assertV3AdapterTargetSafe,
@@ -2339,4 +2584,4 @@ export {
2339
2584
  replaceUnsafeV3AdapterSymlinks,
2340
2585
  writeThroughResolvedPath
2341
2586
  };
2342
- //# sourceMappingURL=chunk-RDPFQODS.js.map
2587
+ //# sourceMappingURL=chunk-WJ6WARGG.js.map