@open-agent-toolkit/cli 0.1.73 → 0.1.76

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.
Files changed (139) hide show
  1. package/assets/config/dispatch-matrix-recommendation.json +13 -17
  2. package/assets/docs/cli-utilities/configuration.md +53 -72
  3. package/assets/docs/cli-utilities/workflow-gates.md +38 -2
  4. package/assets/docs/provider-sync/commands.md +26 -0
  5. package/assets/docs/provider-sync/config.md +17 -6
  6. package/assets/docs/provider-sync/index.md +17 -5
  7. package/assets/docs/provider-sync/manifest-and-drift.md +39 -1
  8. package/assets/docs/provider-sync/providers.md +23 -19
  9. package/assets/docs/provider-sync/scope-and-surface.md +5 -1
  10. package/assets/docs/reference/file-locations.md +9 -3
  11. package/assets/docs/reference/oat-directory-structure.md +11 -3
  12. package/assets/docs/reference/troubleshooting.md +11 -0
  13. package/assets/docs/workflows/projects/artifacts.md +1 -1
  14. package/assets/docs/workflows/projects/dispatch-ceiling.md +29 -33
  15. package/assets/docs/workflows/projects/implementation-execution.md +7 -4
  16. package/assets/docs/workflows/projects/lifecycle.md +4 -3
  17. package/assets/public-package-versions.json +4 -4
  18. package/assets/skills/create-agnostic-skill/SKILL.md +1 -1
  19. package/assets/skills/create-agnostic-skill/references/docs/skills-guide.md +24 -15
  20. package/assets/skills/oat-agent-instructions-analyze/references/docs/provider-reference.md +9 -3
  21. package/assets/skills/oat-agent-instructions-apply/references/docs/provider-reference.md +9 -3
  22. package/assets/skills/oat-dispatch-subagents/SKILL.md +1 -1
  23. package/assets/skills/oat-dispatch-subagents/references/provider-cursor.md +27 -15
  24. package/assets/skills/oat-project-autonomous/SKILL.md +1 -1
  25. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +1 -1
  26. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +1 -1
  27. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +17 -15
  28. package/assets/skills/oat-project-implement/references/dispatch-and-dry-run.md +15 -9
  29. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +1 -1
  30. package/assets/skills/oat-project-implement/references/phase-execution.md +11 -5
  31. package/assets/skills/oat-project-import-plan/SKILL.md +30 -8
  32. package/assets/skills/oat-project-plan/SKILL.md +30 -8
  33. package/assets/skills/oat-project-plan-writing/SKILL.md +12 -9
  34. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +1 -1
  35. package/assets/skills/oat-project-quick-start/SKILL.md +30 -8
  36. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +1 -1
  37. package/assets/skills/oat-project-review-provide/SKILL.md +19 -11
  38. package/assets/skills/oat-project-review-provide-remote/SKILL.md +27 -4
  39. package/dist/commands/config/index.d.ts.map +1 -1
  40. package/dist/commands/config/index.js +55 -0
  41. package/dist/commands/doctor/index.d.ts +2 -1
  42. package/dist/commands/doctor/index.d.ts.map +1 -1
  43. package/dist/commands/doctor/index.js +37 -1
  44. package/dist/commands/gate/index.d.ts +1 -1
  45. package/dist/commands/gate/index.d.ts.map +1 -1
  46. package/dist/commands/gate/index.js +35 -3
  47. package/dist/commands/init/index.d.ts +7 -3
  48. package/dist/commands/init/index.d.ts.map +1 -1
  49. package/dist/commands/init/index.js +129 -38
  50. package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
  51. package/dist/commands/project/dispatch-ceiling/index.js +17 -9
  52. package/dist/commands/remove/skill/remove-skill.d.ts +2 -0
  53. package/dist/commands/remove/skill/remove-skill.d.ts.map +1 -1
  54. package/dist/commands/remove/skill/remove-skill.js +20 -2
  55. package/dist/commands/shared/adopt-stray.d.ts.map +1 -1
  56. package/dist/commands/shared/adopt-stray.js +3 -0
  57. package/dist/commands/shared/codex-strays.d.ts +9 -0
  58. package/dist/commands/shared/codex-strays.d.ts.map +1 -1
  59. package/dist/commands/shared/codex-strays.js +4 -0
  60. package/dist/commands/shared/cursor-skill-disposition.d.ts +17 -0
  61. package/dist/commands/shared/cursor-skill-disposition.d.ts.map +1 -0
  62. package/dist/commands/shared/cursor-skill-disposition.js +41 -0
  63. package/dist/commands/status/index.d.ts +17 -5
  64. package/dist/commands/status/index.d.ts.map +1 -1
  65. package/dist/commands/status/index.js +151 -47
  66. package/dist/commands/sync/apply.d.ts.map +1 -1
  67. package/dist/commands/sync/apply.js +50 -24
  68. package/dist/commands/sync/dry-run.d.ts.map +1 -1
  69. package/dist/commands/sync/dry-run.js +31 -18
  70. package/dist/commands/sync/index.d.ts.map +1 -1
  71. package/dist/commands/sync/index.js +34 -25
  72. package/dist/commands/sync/sync.types.d.ts +26 -11
  73. package/dist/commands/sync/sync.types.d.ts.map +1 -1
  74. package/dist/commands/sync/sync.utils.d.ts.map +1 -1
  75. package/dist/commands/sync/sync.utils.js +3 -2
  76. package/dist/config/oat-config.d.ts +0 -1
  77. package/dist/config/oat-config.d.ts.map +1 -1
  78. package/dist/config/oat-config.js +26 -23
  79. package/dist/config/sync-config.d.ts +6 -2
  80. package/dist/config/sync-config.d.ts.map +1 -1
  81. package/dist/config/sync-config.js +18 -6
  82. package/dist/config/user-sync-config.d.ts +11 -0
  83. package/dist/config/user-sync-config.d.ts.map +1 -0
  84. package/dist/config/user-sync-config.js +61 -0
  85. package/dist/drift/strays.d.ts +1 -1
  86. package/dist/drift/strays.d.ts.map +1 -1
  87. package/dist/drift/strays.js +2 -1
  88. package/dist/engine/compute-plan.d.ts.map +1 -1
  89. package/dist/engine/compute-plan.js +154 -4
  90. package/dist/engine/engine.types.d.ts +2 -2
  91. package/dist/engine/engine.types.d.ts.map +1 -1
  92. package/dist/engine/engine.types.js +1 -0
  93. package/dist/engine/execute-plan.d.ts.map +1 -1
  94. package/dist/engine/execute-plan.js +4 -0
  95. package/dist/engine/index.d.ts +1 -1
  96. package/dist/engine/index.d.ts.map +1 -1
  97. package/dist/engine/index.js +1 -1
  98. package/dist/engine/scanner.d.ts +3 -1
  99. package/dist/engine/scanner.d.ts.map +1 -1
  100. package/dist/engine/scanner.js +4 -2
  101. package/dist/manifest/manifest.types.d.ts +12 -12
  102. package/dist/providers/ceiling/registry.d.ts +2 -0
  103. package/dist/providers/ceiling/registry.d.ts.map +1 -1
  104. package/dist/providers/ceiling/registry.js +20 -4
  105. package/dist/providers/codex/codec/sync-extension.d.ts +11 -10
  106. package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
  107. package/dist/providers/codex/codec/sync-extension.js +42 -14
  108. package/dist/providers/cursor/codec/catalog.d.ts +18 -0
  109. package/dist/providers/cursor/codec/catalog.d.ts.map +1 -0
  110. package/dist/providers/cursor/codec/catalog.js +39 -0
  111. package/dist/providers/cursor/codec/materialize.d.ts +24 -0
  112. package/dist/providers/cursor/codec/materialize.d.ts.map +1 -0
  113. package/dist/providers/cursor/codec/materialize.js +166 -0
  114. package/dist/providers/cursor/codec/shared.d.ts +14 -0
  115. package/dist/providers/cursor/codec/shared.d.ts.map +1 -0
  116. package/dist/providers/cursor/codec/shared.js +76 -0
  117. package/dist/providers/cursor/codec/sync-extension.d.ts +37 -0
  118. package/dist/providers/cursor/codec/sync-extension.d.ts.map +1 -0
  119. package/dist/providers/cursor/codec/sync-extension.js +393 -0
  120. package/dist/providers/cursor/index.d.ts +3 -0
  121. package/dist/providers/cursor/index.d.ts.map +1 -1
  122. package/dist/providers/cursor/index.js +3 -0
  123. package/dist/providers/cursor/paths.d.ts.map +1 -1
  124. package/dist/providers/cursor/paths.js +6 -4
  125. package/dist/providers/identity/availability.d.ts +7 -0
  126. package/dist/providers/identity/availability.d.ts.map +1 -1
  127. package/dist/providers/identity/availability.js +23 -0
  128. package/dist/providers/shared/adapter.types.d.ts +6 -0
  129. package/dist/providers/shared/adapter.types.d.ts.map +1 -1
  130. package/dist/providers/shared/adapter.utils.d.ts +2 -1
  131. package/dist/providers/shared/adapter.utils.d.ts.map +1 -1
  132. package/dist/providers/shared/adapter.utils.js +29 -6
  133. package/dist/providers/shared/index.d.ts +2 -0
  134. package/dist/providers/shared/index.d.ts.map +1 -1
  135. package/dist/providers/shared/index.js +1 -0
  136. package/dist/providers/shared/materialization-extension.d.ts +44 -0
  137. package/dist/providers/shared/materialization-extension.d.ts.map +1 -0
  138. package/dist/providers/shared/materialization-extension.js +26 -0
  139. package/package.json +2 -2
@@ -6,15 +6,17 @@ import { applyOatCoreGitignore, } from '../init/gitignore.js';
6
6
  import { applyGitignore } from '../local/apply.js';
7
7
  import { addLocalPaths } from '../local/manage.js';
8
8
  import { adoptStrayToCanonical, isAdoptionConflictError, } from '../shared/adopt-stray.js';
9
- import { detectCodexRoleStrays, regenerateCodexAfterAdoption, } from '../shared/codex-strays.js';
9
+ import { detectCodexRoleStrays, filterMaterializationManagedStrays, regenerateCodexAfterAdoption, } from '../shared/codex-strays.js';
10
+ import { applyCursorSkillDisposition, isCursorSkillCandidate, } from '../shared/cursor-skill-disposition.js';
10
11
  import { PROVIDER_CONFIG_REMEDIATION } from '../shared/messages.js';
11
12
  import { withScopeOption } from '../shared/scope-option.js';
12
13
  import { confirmAction, inputWithDefault, selectManyWithAbort, selectWithAbort, } from '../shared/shared.prompts.js';
13
14
  import { readGlobalOptions, resolveConcreteScopes, } from '../shared/shared.utils.js';
14
15
  import { DEFAULT_SYNC_CONFIG, loadSyncConfig, saveSyncConfig, } from '../../config/index.js';
15
- import { detectDefaultBranch, readUserConfig, readOatConfig, resolveLocalPaths, writeOatConfig, } from '../../config/oat-config.js';
16
+ import { detectDefaultBranch, readOatConfig, resolveLocalPaths, writeOatConfig, } from '../../config/oat-config.js';
17
+ import { resolveUserSyncConfig } from '../../config/user-sync-config.js';
16
18
  import { detectStrays, filterKnownStrays, } from '../../drift/index.js';
17
- import { configureLocalHooksPath, getHookInstallInfo, installHook, isHookInstalled, scanCanonical, uninstallHook, } from '../../engine/index.js';
19
+ import { configureLocalHooksPath, getHookInstallInfo, installHook, isHookInstalled, scanBundledManagedAgents, scanCanonical, uninstallHook, } from '../../engine/index.js';
18
20
  import { dirExists, fileExists } from '../../fs/io.js';
19
21
  import { resolveProjectRoot, resolveScopeRoot } from '../../fs/paths.js';
20
22
  import { normalizeToPosixPath } from '../../fs/paths.js';
@@ -24,8 +26,10 @@ import { codexAdapter } from '../../providers/codex/index.js';
24
26
  import { applyCodexProjectExtensionPlan, computeCodexProjectExtensionPlan, } from '../../providers/codex/codec/sync-extension.js';
25
27
  import { copilotAdapter } from '../../providers/copilot/index.js';
26
28
  import { cursorAdapter } from '../../providers/cursor/index.js';
29
+ import { applyCursorProjectExtensionPlan, computeCursorProjectExtensionPlan, } from '../../providers/cursor/codec/sync-extension.js';
27
30
  import { geminiAdapter } from '../../providers/gemini/index.js';
28
- import { getActiveAdapters, getConfigAwareAdapters, getSyncMappings, } from '../../providers/shared/index.js';
31
+ import { getActiveAdapters, getConfigAwareAdapters, } from '../../providers/shared/index.js';
32
+ import { getAdoptionSources } from '../../providers/shared/adapter.utils.js';
29
33
  import { Command } from 'commander';
30
34
  import { detectExistingDocs, } from './detect-docs.js';
31
35
  import { createInitToolsCommand, runInitToolsWithDefaults, } from './tools/index.js';
@@ -49,15 +53,43 @@ async function ensureCanonicalDirectories(scopeRoot, scope) {
49
53
  await mkdir(join(scopeRoot, '.agents', 'rules'), { recursive: true });
50
54
  }
51
55
  }
52
- async function collectStraysDefault(scopeRoot, scope, manifest, canonicalEntries, activeAdapters) {
56
+ async function collectStraysDefault(scopeRoot, scope, manifest, canonicalEntries, activeAdapters, userConfigDir = join(scopeRoot, '.oat')) {
53
57
  const adaptersToScan = activeAdapters ??
54
58
  (await getActiveAdapters(getDefaultAdapters(), scopeRoot));
55
59
  const candidates = [];
60
+ const hasMaterializationAdapter = adaptersToScan.some((adapter) => adapter.name === 'codex' || adapter.name === 'cursor');
61
+ const materializationCanonicalEntries = scope === 'user' && hasMaterializationAdapter
62
+ ? [...canonicalEntries, ...(await scanBundledManagedAgents())]
63
+ : canonicalEntries;
64
+ const codexExtensionPlan = adaptersToScan.some((adapter) => adapter.name === 'codex')
65
+ ? await computeCodexProjectExtensionPlan(scopeRoot, materializationCanonicalEntries, undefined, { userConfigDir })
66
+ : undefined;
67
+ const cursorExtensionPlan = adaptersToScan.some((adapter) => adapter.name === 'cursor')
68
+ ? await computeCursorProjectExtensionPlan(scopeRoot, materializationCanonicalEntries, undefined, { userConfigDir })
69
+ : undefined;
70
+ const materializationPlans = [
71
+ ...(codexExtensionPlan
72
+ ? [
73
+ {
74
+ provider: 'codex',
75
+ operations: codexExtensionPlan.operations.map((operation) => ({
76
+ ...operation,
77
+ provider: 'codex',
78
+ entryName: operation.roleName,
79
+ })),
80
+ managedEntries: codexExtensionPlan.managedRoles,
81
+ aggregateHash: codexExtensionPlan.aggregateConfigHash,
82
+ metadata: codexExtensionPlan.metadata,
83
+ },
84
+ ]
85
+ : []),
86
+ ...(cursorExtensionPlan ? [cursorExtensionPlan] : []),
87
+ ];
56
88
  for (const adapter of adaptersToScan) {
57
- const mappings = getSyncMappings(adapter, scope);
58
- for (const mapping of mappings) {
59
- const providerDir = join(scopeRoot, mapping.providerDir);
60
- const strays = await detectStrays(adapter.name, providerDir, manifest, canonicalEntries, mapping);
89
+ const adoptionSources = getAdoptionSources(adapter, scope);
90
+ for (const source of adoptionSources) {
91
+ const providerDir = join(scopeRoot, source.directory);
92
+ const strays = filterMaterializationManagedStrays((await detectStrays(adapter.name, providerDir, manifest, canonicalEntries, source.mapping)).map((report) => ({ provider: adapter.name, report })), materializationPlans).map((candidate) => candidate.report);
61
93
  for (const report of strays) {
62
94
  if (report.state.status !== 'stray') {
63
95
  continue;
@@ -65,14 +97,12 @@ async function collectStraysDefault(scopeRoot, scope, manifest, canonicalEntries
65
97
  candidates.push({
66
98
  provider: adapter.name,
67
99
  report,
68
- mapping,
100
+ mapping: source.mapping,
69
101
  });
70
102
  }
71
103
  }
72
104
  }
73
- if (scope === 'project' &&
74
- adaptersToScan.some((adapter) => adapter.name === 'codex')) {
75
- const codexExtensionPlan = await computeCodexProjectExtensionPlan(scopeRoot, canonicalEntries);
105
+ if (adaptersToScan.some((adapter) => adapter.name === 'codex')) {
76
106
  const codexStrays = await detectCodexRoleStrays(scopeRoot, canonicalEntries, new Set(codexExtensionPlan.managedRoles));
77
107
  for (const stray of codexStrays) {
78
108
  candidates.push({
@@ -131,7 +161,7 @@ function createDependencies() {
131
161
  async loadSyncConfig(configPath) {
132
162
  return loadSyncConfig(configPath, DEFAULT_SYNC_CONFIG);
133
163
  },
134
- readUserConfig,
164
+ resolveUserSyncConfig,
135
165
  saveSyncConfig,
136
166
  getConfigAwareAdapters,
137
167
  applyOatCoreGitignore,
@@ -146,6 +176,7 @@ function createDependencies() {
146
176
  fileExists,
147
177
  inputWithDefault,
148
178
  selectWithAbort,
179
+ applyCursorSkillDisposition,
149
180
  runGuidedSetup: runGuidedSetupImpl,
150
181
  runToolPacks: runInitToolsWithDefaults,
151
182
  async runProviderSync(projectRoot) {
@@ -427,12 +458,13 @@ async function runInitCommand(context, dependencies, hookFlag, setupFlag) {
427
458
  let projectRoot = null;
428
459
  let oatDirExistedBefore = true;
429
460
  const scopeSummaries = [];
461
+ let migrationAborted = false;
430
462
  for (const scope of scopes) {
431
463
  const scopeRoot = await dependencies.resolveScopeRoot(scope, context);
432
464
  const syncConfigPath = join(scopeRoot, '.oat', 'sync', 'config.json');
433
465
  const userConfigDir = join(context.home, '.oat');
434
466
  let syncConfig = await dependencies.loadSyncConfig(syncConfigPath);
435
- const userConfig = await dependencies.readUserConfig(userConfigDir);
467
+ const userSyncConfig = await dependencies.resolveUserSyncConfig(userConfigDir);
436
468
  let activeAdaptersForStrays;
437
469
  if (scope === 'project') {
438
470
  projectRoot = scopeRoot;
@@ -481,51 +513,105 @@ async function runInitCommand(context, dependencies, hookFlag, setupFlag) {
481
513
  manifest = createEmptyManifest();
482
514
  }
483
515
  const canonicalEntries = await dependencies.scanCanonical(scopeRoot, scope);
484
- const collectedStrays = await dependencies.collectStrays(scopeRoot, scope, manifest, canonicalEntries, activeAdaptersForStrays);
516
+ const collectedStrays = await dependencies.collectStrays(scopeRoot, scope, manifest, canonicalEntries, activeAdaptersForStrays, userConfigDir);
485
517
  const { candidates: strays } = filterKnownStrays({
486
518
  reports: collectedStrays.map((stray) => stray.report),
487
519
  candidates: collectedStrays,
488
520
  knownStrays: {
489
521
  project: syncConfig.knownStrays,
490
- user: userConfig.knownStrays,
522
+ user: userSyncConfig.knownStrays,
491
523
  },
492
524
  });
493
525
  let straysAdopted = 0;
526
+ let codexStrayAdopted = false;
527
+ const cursorSkillStrays = strays.filter(isCursorSkillCandidate);
528
+ const ordinaryStrays = strays.filter((stray) => !isCursorSkillCandidate(stray));
494
529
  if (!context.interactive && strays.length > 0) {
495
530
  context.logger.warn(ADOPT_REMEDIATION);
496
531
  }
497
- if (context.interactive && strays.length > 0) {
498
- const choices = strays.map((stray, index) => ({
499
- label: formatStrayChoiceLabel(scope, stray.report.providerPath, stray.provider),
500
- value: String(index),
501
- description: formatPathForScope(scope, stray.report.providerPath),
502
- }));
503
- const selectedValues = await dependencies.selectManyWithAbort(`Select stray entries to adopt [${scope}]`, choices, { interactive: context.interactive });
504
- const selectedIndices = new Set((selectedValues ?? []).map((value) => Number.parseInt(value, 10)));
505
- let codexStrayAdopted = false;
506
- for (const [index, stray] of strays.entries()) {
507
- if (!selectedIndices.has(index)) {
508
- continue;
532
+ if (context.interactive && strays.length > 0 && !migrationAborted) {
533
+ for (const stray of cursorSkillStrays) {
534
+ const disposition = await dependencies.selectWithAbort(`Migrate Cursor skill [${scope}]: ${formatPathForScope(scope, stray.report.providerPath)}`, [
535
+ {
536
+ label: 'Adopt into canonical',
537
+ value: 'adopt',
538
+ description: `Move to ${stray.mapping.canonicalDir}.`,
539
+ },
540
+ {
541
+ label: 'Keep Cursor-only',
542
+ value: 'keep',
543
+ description: 'Leave in .cursor/skills and remember this choice.',
544
+ },
545
+ ], { interactive: context.interactive });
546
+ if (disposition === null) {
547
+ migrationAborted = true;
548
+ break;
509
549
  }
510
550
  try {
511
- manifest = await dependencies.adoptStray(scopeRoot, stray, manifest);
512
- straysAdopted += 1;
513
- codexStrayAdopted = codexStrayAdopted || stray.provider === 'codex';
551
+ manifest = await dependencies.applyCursorSkillDisposition(scopeRoot, stray, manifest, disposition, syncConfigPath);
552
+ if (disposition === 'adopt') {
553
+ straysAdopted += 1;
554
+ }
555
+ else {
556
+ context.logger.success(`Kept Cursor-only [${scope}]: ${formatPathForScope(scope, stray.report.providerPath)}.`);
557
+ }
514
558
  }
515
559
  catch (error) {
516
- if (!isAdoptionConflictError(error)) {
560
+ if (error instanceof Error &&
561
+ error.message.startsWith('Cannot keep ')) {
562
+ context.logger.warn(error.message);
563
+ continue;
564
+ }
565
+ if (!isAdoptionConflictError(error) || disposition !== 'adopt') {
517
566
  throw error;
518
567
  }
519
568
  const shouldReplace = await dependencies.confirmAction(`Conflict detected for ${formatPathForScope(scope, stray.report.providerPath)}. Replace canonical content with stray content?`, { interactive: context.interactive });
520
569
  if (!shouldReplace) {
521
- context.logger.warn(`Skipped conflicting stray entry [${scope}]: ${formatPathForScope(scope, stray.report.providerPath)}`);
570
+ context.logger.warn(`Skipped conflicting Cursor skill [${scope}]: ${formatPathForScope(scope, stray.report.providerPath)}`);
522
571
  continue;
523
572
  }
524
- manifest = await dependencies.adoptStray(scopeRoot, stray, manifest, {
525
- replaceCanonical: true,
526
- });
573
+ manifest = await dependencies.applyCursorSkillDisposition(scopeRoot, stray, manifest, disposition, syncConfigPath, { replaceCanonical: true });
527
574
  straysAdopted += 1;
528
- codexStrayAdopted = codexStrayAdopted || stray.provider === 'codex';
575
+ }
576
+ }
577
+ if (!migrationAborted && ordinaryStrays.length > 0) {
578
+ const selectedValues = await dependencies.selectManyWithAbort(`Select stray entries to adopt [${scope}]`, ordinaryStrays.map((stray, index) => ({
579
+ label: formatStrayChoiceLabel(scope, stray.report.providerPath, stray.provider),
580
+ value: String(index),
581
+ description: formatPathForScope(scope, stray.report.providerPath),
582
+ })), { interactive: context.interactive });
583
+ if (selectedValues === null) {
584
+ migrationAborted = true;
585
+ }
586
+ else {
587
+ const selectedIndices = new Set(selectedValues.map((value) => Number.parseInt(value, 10)));
588
+ for (const [index, stray] of ordinaryStrays.entries()) {
589
+ if (!selectedIndices.has(index)) {
590
+ continue;
591
+ }
592
+ try {
593
+ manifest = await dependencies.adoptStray(scopeRoot, stray, manifest);
594
+ straysAdopted += 1;
595
+ codexStrayAdopted =
596
+ codexStrayAdopted || stray.provider === 'codex';
597
+ }
598
+ catch (error) {
599
+ if (!isAdoptionConflictError(error)) {
600
+ throw error;
601
+ }
602
+ const shouldReplace = await dependencies.confirmAction(`Conflict detected for ${formatPathForScope(scope, stray.report.providerPath)}. Replace canonical content with stray content?`, { interactive: context.interactive });
603
+ if (!shouldReplace) {
604
+ context.logger.warn(`Skipped conflicting stray entry [${scope}]: ${formatPathForScope(scope, stray.report.providerPath)}`);
605
+ continue;
606
+ }
607
+ manifest = await dependencies.adoptStray(scopeRoot, stray, manifest, {
608
+ replaceCanonical: true,
609
+ });
610
+ straysAdopted += 1;
611
+ codexStrayAdopted =
612
+ codexStrayAdopted || stray.provider === 'codex';
613
+ }
614
+ }
529
615
  }
530
616
  }
531
617
  if (codexStrayAdopted && scope === 'project') {
@@ -535,6 +621,11 @@ async function runInitCommand(context, dependencies, hookFlag, setupFlag) {
535
621
  computeExtensionPlan: computeCodexProjectExtensionPlan,
536
622
  applyExtensionPlan: applyCodexProjectExtensionPlan,
537
623
  });
624
+ if (activeAdaptersForStrays?.some((adapter) => adapter.name === 'cursor')) {
625
+ const refreshedCanonical = await dependencies.scanCanonical(scopeRoot, scope);
626
+ const cursorPlan = await computeCursorProjectExtensionPlan(scopeRoot, refreshedCanonical, undefined, { userConfigDir });
627
+ await applyCursorProjectExtensionPlan(scopeRoot, cursorPlan);
628
+ }
538
629
  }
539
630
  if (straysAdopted > 0) {
540
631
  context.logger.success(`Adopted ${straysAdopted} stray entr${straysAdopted === 1 ? 'y' : 'ies'} [${scope}].`);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/dispatch-ceiling/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAa9B,OAAO,EAUL,KAAK,uBAAuB,EAY7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAsBzB,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAiC5C,UAAU,2BAA2B;IACnC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC7E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAqmFD,wBAAgB,mCAAmC,CACjD,SAAS,GAAE,OAAO,CAAC,2BAA2B,CAAM,GACnD,OAAO,CAkGT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/dispatch-ceiling/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAa9B,OAAO,EAUL,KAAK,uBAAuB,EAY7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAsBzB,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAiC5C,UAAU,2BAA2B;IACnC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC7E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAmnFD,wBAAgB,mCAAmC,CACjD,SAAS,GAAE,OAAO,CAAC,2BAA2B,CAAM,GACnD,OAAO,CAkGT"}
@@ -210,10 +210,10 @@ function uncappedPreferredTier(provider, role, preferredValue) {
210
210
  }
211
211
  function dispatchValueFromRouteTarget(target) {
212
212
  const adapter = getCeilingAdapter(target.harness);
213
- if (adapter.mechanism === 'pinned-variant') {
213
+ if (adapter.selectionAxis === 'model-effort') {
214
214
  return target.effort ?? null;
215
215
  }
216
- if (adapter.mechanism === 'model-arg') {
216
+ if (adapter.selectionAxis === 'model' || adapter.selectionAxis === 'tier') {
217
217
  return target.model ?? null;
218
218
  }
219
219
  return null;
@@ -226,7 +226,7 @@ function routeTargetFromBareValue(provider, value, routeIndex, routeLength) {
226
226
  routeIndex,
227
227
  routeLength,
228
228
  };
229
- if (adapter.mechanism === 'pinned-variant') {
229
+ if (adapter.selectionAxis === 'model-effort') {
230
230
  target.effort = value;
231
231
  }
232
232
  else {
@@ -398,13 +398,15 @@ function candidatePrimaryTarget(provider, tier, candidate) {
398
398
  throw new Error(`Malformed ${provider} candidate ordering in ${tier}: direct dispatch role names are not candidate models.`);
399
399
  }
400
400
  const targetAdapter = getCeilingAdapter(target.harness);
401
- if (targetAdapter.mechanism === 'pinned-variant' &&
401
+ if (targetAdapter.selectionAxis === 'model-effort' &&
402
402
  (!target.model ||
403
403
  !target.effort ||
404
404
  !CODEX_VALUES.includes(target.effort))) {
405
405
  throw new Error(`Malformed ${provider} candidate ordering in ${tier}: Codex candidates require a model and supported effort.`);
406
406
  }
407
- if (targetAdapter.mechanism === 'model-arg' && !target.model) {
407
+ if ((targetAdapter.selectionAxis === 'model' ||
408
+ targetAdapter.selectionAxis === 'tier') &&
409
+ !target.model) {
408
410
  throw new Error(`Malformed ${provider} candidate ordering in ${tier}: model-argument candidates require a model.`);
409
411
  }
410
412
  return target;
@@ -1053,7 +1055,7 @@ function selectDispatchValue(provider, role, policy, preferredValue) {
1053
1055
  target: selectedTarget,
1054
1056
  };
1055
1057
  }
1056
- function hasCodexVariantDispatchArgs(dispatchArgs) {
1058
+ function hasVariantDispatchArgs(dispatchArgs) {
1057
1059
  return (dispatchArgs !== null &&
1058
1060
  'variant' in dispatchArgs &&
1059
1061
  typeof dispatchArgs.variant === 'string' &&
@@ -1065,20 +1067,26 @@ function hasModelDispatchArgs(dispatchArgs) {
1065
1067
  typeof dispatchArgs.model === 'string' &&
1066
1068
  dispatchArgs.model.length > 0);
1067
1069
  }
1068
- function modelAxis(selection, dispatchArgs) {
1070
+ function modelAxis(provider, selection, dispatchArgs) {
1069
1071
  if (selection.target?.model && dispatchArgs) {
1070
1072
  return `selected:${selection.target.model}`;
1071
1073
  }
1072
1074
  if (hasModelDispatchArgs(dispatchArgs)) {
1073
1075
  return `selected:${dispatchArgs.model}`;
1074
1076
  }
1077
+ if (provider === 'cursor' &&
1078
+ hasVariantDispatchArgs(dispatchArgs) &&
1079
+ selection.target === null &&
1080
+ selection.selectedValue) {
1081
+ return `selected:${selection.selectedValue}`;
1082
+ }
1075
1083
  if (selection.selectionMode === 'inherit-default') {
1076
1084
  return 'inherited';
1077
1085
  }
1078
1086
  return 'unresolved';
1079
1087
  }
1080
1088
  function codexEffortAxis(selection, dispatchArgs) {
1081
- if (hasCodexVariantDispatchArgs(dispatchArgs) && selection.target?.effort) {
1089
+ if (hasVariantDispatchArgs(dispatchArgs) && selection.target?.effort) {
1082
1090
  return `selected:${selection.target.effort}`;
1083
1091
  }
1084
1092
  if (selection.selectionMode === 'inherit-default' ||
@@ -1149,7 +1157,7 @@ function buildProviderResolution(provider, policy, role, orchestratorTier, prefe
1149
1157
  mode,
1150
1158
  mechanism: adapter.mechanism,
1151
1159
  dispatchArgs,
1152
- modelAxis: modelAxis(selection, dispatchArgs),
1160
+ modelAxis: modelAxis(provider, selection, dispatchArgs),
1153
1161
  effortAxis: provider === 'codex'
1154
1162
  ? codexEffortAxis(selection, dispatchArgs)
1155
1163
  : 'not-applicable',
@@ -3,6 +3,7 @@ import { readGlobalOptions } from '../../shared/shared.utils.js';
3
3
  import { type SyncConfig } from '../../../config/sync-config.js';
4
4
  import { type Manifest } from '../../../manifest/index.js';
5
5
  import { type ConfigAwareAdaptersResult, type PathMapping, type ProviderAdapter } from '../../../providers/shared/index.js';
6
+ import type { AdoptionSource } from '../../../providers/shared/adapter.types.js';
6
7
  import type { ConcreteScope, Scope } from '../../../shared/types.js';
7
8
  import { Command } from 'commander';
8
9
  export interface RemoveSkillDependencies {
@@ -14,6 +15,7 @@ export interface RemoveSkillDependencies {
14
15
  getAdapters: () => ProviderAdapter[];
15
16
  getConfigAwareAdapters: (adapters: ProviderAdapter[], scopeRoot: string, config: SyncConfig) => Promise<ConfigAwareAdaptersResult>;
16
17
  getSyncMappings: (adapter: ProviderAdapter, scope: Scope) => PathMapping[];
18
+ getAdoptionSources: (adapter: ProviderAdapter, scope: Scope) => AdoptionSource[];
17
19
  pathExists: (path: string) => Promise<boolean>;
18
20
  removeDirectory: (path: string) => Promise<void>;
19
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"remove-skill.d.ts","sourceRoot":"","sources":["../../../../src/commands/remove/skill/remove-skill.ts"],"names":[],"mappings":"AAGA,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEhF,OAAO,EACL,iBAAiB,EAElB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAEL,KAAK,QAAQ,EAGd,MAAM,iBAAiB,CAAC;AAMzB,OAAO,EACL,KAAK,yBAAyB,EAG9B,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8BpC,MAAM,WAAW,uBAAuB;IACtC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,KAC1C,cAAc,CAAC;IACpB,gBAAgB,EAAE,CAChB,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,cAAc,KACpB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1D,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,cAAc,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5D,WAAW,EAAE,MAAM,eAAe,EAAE,CAAC;IACrC,sBAAsB,EAAE,CACtB,QAAQ,EAAE,eAAe,EAAE,EAC3B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,KACf,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACxC,eAAe,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,KAAK,WAAW,EAAE,CAAC;IAC3E,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAED,wBAAgB,oCAAoC,IAAI,uBAAuB,CAgC9E;AAgKD,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,OAAO,EACf,YAAY,EAAE,uBAAuB,GACpC,OAAO,CAAC,OAAO,CAAC,CAsDlB;AAED,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAwCT"}
1
+ {"version":3,"file":"remove-skill.d.ts","sourceRoot":"","sources":["../../../../src/commands/remove/skill/remove-skill.ts"],"names":[],"mappings":"AAGA,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEhF,OAAO,EACL,iBAAiB,EAElB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAEL,KAAK,QAAQ,EAGd,MAAM,iBAAiB,CAAC;AAMzB,OAAO,EACL,KAAK,yBAAyB,EAG9B,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA8BpC,MAAM,WAAW,uBAAuB;IACtC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,KAC1C,cAAc,CAAC;IACpB,gBAAgB,EAAE,CAChB,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,cAAc,KACpB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1D,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,cAAc,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5D,WAAW,EAAE,MAAM,eAAe,EAAE,CAAC;IACrC,sBAAsB,EAAE,CACtB,QAAQ,EAAE,eAAe,EAAE,EAC3B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,KACf,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACxC,eAAe,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,KAAK,WAAW,EAAE,CAAC;IAC3E,kBAAkB,EAAE,CAClB,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,KAAK,KACT,cAAc,EAAE,CAAC;IACtB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAED,wBAAgB,oCAAoC,IAAI,uBAAuB,CAiC9E;AAuLD,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,OAAO,EACf,YAAY,EAAE,uBAAuB,GACpC,OAAO,CAAC,OAAO,CAAC,CAsDlB;AAED,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAwCT"}
@@ -13,6 +13,7 @@ import { copilotAdapter } from '../../../providers/copilot/index.js';
13
13
  import { cursorAdapter } from '../../../providers/cursor/index.js';
14
14
  import { geminiAdapter } from '../../../providers/gemini/index.js';
15
15
  import { getConfigAwareAdapters, getSyncMappings, } from '../../../providers/shared/index.js';
16
+ import { getAdoptionSources } from '../../../providers/shared/adapter.utils.js';
16
17
  import { Command } from 'commander';
17
18
  export function createDefaultRemoveSkillDependencies() {
18
19
  return {
@@ -39,6 +40,7 @@ export function createDefaultRemoveSkillDependencies() {
39
40
  },
40
41
  getConfigAwareAdapters,
41
42
  getSyncMappings,
43
+ getAdoptionSources,
42
44
  async pathExists(path) {
43
45
  return (await fileExists(path)) || (await dirExists(path));
44
46
  },
@@ -76,10 +78,10 @@ async function buildScopePlan(scope, scopeRoot, skillName, dependencies) {
76
78
  const managedProviderViews = [];
77
79
  const unmanagedProviderViews = [];
78
80
  for (const adapter of activeAdapters) {
79
- const mappings = dependencies
81
+ const syncMappings = dependencies
80
82
  .getSyncMappings(adapter, scope)
81
83
  .filter((mapping) => mapping.contentType === 'skill');
82
- for (const mapping of mappings) {
84
+ for (const mapping of syncMappings) {
83
85
  const relativePath = join(mapping.providerDir, skillName);
84
86
  const absolutePath = join(scopeRoot, relativePath);
85
87
  const hasManifestEntry = manifest.entries.some((entry) => entry.canonicalPath === canonicalRelativePath &&
@@ -101,6 +103,22 @@ async function buildScopePlan(scope, scopeRoot, skillName, dependencies) {
101
103
  });
102
104
  }
103
105
  }
106
+ const syncProviderDirs = new Set(syncMappings.map((mapping) => mapping.providerDir));
107
+ const localAdoptionSources = dependencies
108
+ .getAdoptionSources(adapter, scope)
109
+ .filter((source) => source.contentType === 'skill' &&
110
+ !syncProviderDirs.has(source.directory));
111
+ for (const source of localAdoptionSources) {
112
+ const relativePath = join(source.directory, skillName);
113
+ const absolutePath = join(scopeRoot, relativePath);
114
+ if (await dependencies.pathExists(absolutePath)) {
115
+ unmanagedProviderViews.push({
116
+ provider: adapter.name,
117
+ absolutePath,
118
+ relativePath,
119
+ });
120
+ }
121
+ }
104
122
  }
105
123
  return {
106
124
  scope,
@@ -1 +1 @@
1
- {"version":3,"file":"adopt-stray.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/adopt-stray.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAKzC,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,0BAA0B,CAAC;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAGnE,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE;QACN,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,YAAY,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,UAAU,iBAAiB;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAEzE;AAWD,wBAAsB,qBAAqB,CACzC,UAAU,SAAS,sBAAsB,EAEzC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,QAAQ,CAAC,CA0EnB"}
1
+ {"version":3,"file":"adopt-stray.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/adopt-stray.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAKzC,OAAO,KAAK,EAAE,QAAQ,EAAiB,MAAM,0BAA0B,CAAC;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAGnE,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE;QACN,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,YAAY,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,UAAU,iBAAiB;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAEzE;AAWD,wBAAsB,qBAAqB,CACzC,UAAU,SAAS,sBAAsB,EAEzC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,QAAQ,CAAC,CA8EnB"}
@@ -53,6 +53,9 @@ export async function adoptStrayToCanonical(scopeRoot, stray, manifest, options
53
53
  else {
54
54
  await rename(providerAbsolutePath, canonicalAbsolutePath);
55
55
  }
56
+ if (stray.mapping.nativeRead) {
57
+ return manifest;
58
+ }
56
59
  const strategy = await createSymlink(canonicalAbsolutePath, providerAbsolutePath, undefined, isFile);
57
60
  const canonicalPath = toPosixPath(relative(scopeRoot, canonicalAbsolutePath));
58
61
  const providerPath = toPosixPath(relative(scopeRoot, providerAbsolutePath));
@@ -1,5 +1,6 @@
1
1
  import type { CanonicalEntry } from '../../engine/index.js';
2
2
  import type { CodexExtensionPlan } from '../../providers/codex/codec/sync-extension.js';
3
+ import type { MaterializationPlan } from '../../providers/shared/index.js';
3
4
  export interface CodexRoleStray {
4
5
  roleName: string;
5
6
  providerPath: string;
@@ -11,6 +12,14 @@ export interface CodexRegenerationDependencies {
11
12
  computeExtensionPlan: (scopeRoot: string, canonicalEntries: CanonicalEntry[]) => Promise<CodexExtensionPlan>;
12
13
  applyExtensionPlan: (scopeRoot: string, plan: CodexExtensionPlan) => Promise<unknown>;
13
14
  }
15
+ interface ProviderStrayCandidate {
16
+ provider: string;
17
+ report: {
18
+ providerPath: string;
19
+ };
20
+ }
21
+ export declare function filterMaterializationManagedStrays<TCandidate extends ProviderStrayCandidate>(candidates: TCandidate[], plans: readonly MaterializationPlan[]): TCandidate[];
14
22
  export declare function detectCodexRoleStrays(scopeRoot: string, canonicalEntries: CanonicalEntry[], managedRoleNames?: Set<string>): Promise<CodexRoleStray[]>;
15
23
  export declare function regenerateCodexAfterAdoption({ scopeRoot, scanCanonical, computeExtensionPlan, applyExtensionPlan, }: CodexRegenerationDependencies): Promise<void>;
24
+ export {};
16
25
  //# sourceMappingURL=codex-strays.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codex-strays.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/codex-strays.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAEhF,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,6BAA6B;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,oBAAoB,EAAE,CACpB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,cAAc,EAAE,KAC/B,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjC,kBAAkB,EAAE,CAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,kBAAkB,KACrB,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB;AAgJD,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,cAAc,EAAE,EAClC,gBAAgB,GAAE,GAAG,CAAC,MAAM,CAAa,GACxC,OAAO,CAAC,cAAc,EAAE,CAAC,CAuB3B;AAED,wBAAsB,4BAA4B,CAAC,EACjD,SAAS,EACT,aAAa,EACb,oBAAoB,EACpB,kBAAkB,GACnB,EAAE,6BAA6B,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/C"}
1
+ {"version":3,"file":"codex-strays.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/codex-strays.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,6BAA6B;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAC/C,oBAAoB,EAAE,CACpB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,cAAc,EAAE,KAC/B,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjC,kBAAkB,EAAE,CAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,kBAAkB,KACrB,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB;AAED,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAClC;AAED,wBAAgB,kCAAkC,CAChD,UAAU,SAAS,sBAAsB,EAEzC,UAAU,EAAE,UAAU,EAAE,EACxB,KAAK,EAAE,SAAS,mBAAmB,EAAE,GACpC,UAAU,EAAE,CAed;AAgJD,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,cAAc,EAAE,EAClC,gBAAgB,GAAE,GAAG,CAAC,MAAM,CAAa,GACxC,OAAO,CAAC,cAAc,EAAE,CAAC,CAuB3B;AAED,wBAAsB,4BAA4B,CAAC,EACjD,SAAS,EACT,aAAa,EACb,oBAAoB,EACpB,kBAAkB,GACnB,EAAE,6BAA6B,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/C"}
@@ -2,6 +2,10 @@ import { readdir, readFile, stat } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import TOML from '@iarna/toml';
4
4
  import { isOatManagedCodexRoleFile } from '../../providers/codex/codec/shared.js';
5
+ export function filterMaterializationManagedStrays(candidates, plans) {
6
+ const managedPaths = new Set(plans.flatMap((plan) => plan.operations.map((operation) => `${plan.provider}|${operation.path.replaceAll('\\', '/')}`)));
7
+ return candidates.filter((candidate) => !managedPaths.has(`${candidate.provider}|${candidate.report.providerPath.replaceAll('\\', '/')}`));
8
+ }
5
9
  function canonicalRoleNames(canonicalEntries) {
6
10
  return new Set(canonicalEntries
7
11
  .filter((entry) => entry.type === 'agent' && entry.isFile)
@@ -0,0 +1,17 @@
1
+ import type { Manifest } from '../../manifest/manifest.types.js';
2
+ import type { PathMapping } from '../../providers/shared/adapter.types.js';
3
+ export type CursorSkillDisposition = 'adopt' | 'keep';
4
+ export interface CursorSkillCandidate {
5
+ provider: string;
6
+ report: {
7
+ providerPath: string;
8
+ };
9
+ mapping: PathMapping;
10
+ }
11
+ interface ApplyCursorSkillDispositionOptions {
12
+ replaceCanonical?: boolean;
13
+ }
14
+ export declare function isCursorSkillCandidate(candidate: CursorSkillCandidate): boolean;
15
+ export declare function applyCursorSkillDisposition(scopeRoot: string, candidate: CursorSkillCandidate, manifest: Manifest, disposition: CursorSkillDisposition, syncConfigPath: string, options?: ApplyCursorSkillDispositionOptions): Promise<Manifest>;
16
+ export {};
17
+ //# sourceMappingURL=cursor-skill-disposition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-skill-disposition.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/cursor-skill-disposition.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAInE,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,MAAM,CAAC;AAEtD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE;QACN,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,UAAU,kCAAkC;IAC1C,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,oBAAoB,GAC9B,OAAO,CAST;AAED,wBAAsB,2BAA2B,CAC/C,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,oBAAoB,EAC/B,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,sBAAsB,EACnC,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE,kCAAuC,GAC/C,OAAO,CAAC,QAAQ,CAAC,CAcnB"}
@@ -0,0 +1,41 @@
1
+ import { access } from 'node:fs/promises';
2
+ import { basename, relative, resolve } from 'node:path';
3
+ import { appendKnownStray } from '../../config/sync-config.js';
4
+ import { CliError } from '../../errors/index.js';
5
+ import { toPosixPath } from '../../fs/paths.js';
6
+ import { adoptStrayToCanonical } from './adopt-stray.js';
7
+ export function isCursorSkillCandidate(candidate) {
8
+ const providerPath = toPosixPath(candidate.report.providerPath);
9
+ return (candidate.provider === 'cursor' &&
10
+ candidate.mapping.contentType === 'skill' &&
11
+ candidate.mapping.nativeRead &&
12
+ (providerPath === '.cursor/skills' ||
13
+ providerPath.startsWith('.cursor/skills/')));
14
+ }
15
+ export async function applyCursorSkillDisposition(scopeRoot, candidate, manifest, disposition, syncConfigPath, options = {}) {
16
+ if (!isCursorSkillCandidate(candidate)) {
17
+ throw new CliError(`Cannot apply a Cursor skill disposition to ${candidate.report.providerPath}.`);
18
+ }
19
+ if (disposition === 'adopt') {
20
+ return adoptStrayToCanonical(scopeRoot, candidate, manifest, options);
21
+ }
22
+ await assertCursorOnlyNameAvailable(scopeRoot, candidate);
23
+ await appendKnownStray(syncConfigPath, candidate.report.providerPath);
24
+ return manifest;
25
+ }
26
+ async function assertCursorOnlyNameAvailable(scopeRoot, candidate) {
27
+ const canonicalPath = resolve(scopeRoot, candidate.mapping.canonicalDir, basename(candidate.report.providerPath));
28
+ if (!(await pathExists(canonicalPath))) {
29
+ return;
30
+ }
31
+ throw new CliError(`Cannot keep ${candidate.report.providerPath} Cursor-only because canonical skill ${toPosixPath(relative(scopeRoot, canonicalPath))} has the same name. Rename one skill, then run the command again.`);
32
+ }
33
+ async function pathExists(path) {
34
+ try {
35
+ await access(path);
36
+ return true;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
@@ -1,13 +1,15 @@
1
1
  import { type CommandContext, type GlobalOptions } from '../../app/command-context.js';
2
2
  import { type CodexRoleStray } from '../shared/codex-strays.js';
3
- import { type MultiSelectChoice, type PromptContext } from '../shared/shared.prompts.js';
3
+ import { type CursorSkillDisposition } from '../shared/cursor-skill-disposition.js';
4
+ import { type MultiSelectChoice, type PromptContext, type SelectChoice } from '../shared/shared.prompts.js';
4
5
  import { type SyncConfig } from '../../config/index.js';
5
- import { type UserConfig } from '../../config/oat-config.js';
6
6
  import { type CopyTransform, type DriftReport } from '../../drift/index.js';
7
7
  import { type CanonicalEntry } from '../../engine/index.js';
8
8
  import type { Manifest } from '../../manifest/index.js';
9
9
  import { type CodexExtensionPlan } from '../../providers/codex/codec/sync-extension.js';
10
+ import { type CursorExtensionPlan } from '../../providers/cursor/codec/sync-extension.js';
10
11
  import { type PathMapping, type ProviderAdapter } from '../../providers/shared/index.js';
12
+ import type { AdoptionSource } from '../../providers/shared/adapter.types.js';
11
13
  import { type ConcreteScope, type Scope } from '../../shared/types.js';
12
14
  import { Command } from 'commander';
13
15
  interface StatusDependencies {
@@ -15,26 +17,36 @@ interface StatusDependencies {
15
17
  resolveScopeRoot: (scope: ConcreteScope, context: CommandContext) => Promise<string>;
16
18
  loadManifest: (manifestPath: string) => Promise<Manifest>;
17
19
  loadSyncConfig: (configPath: string) => Promise<SyncConfig>;
18
- readUserConfig: (userConfigDir: string) => Promise<UserConfig>;
20
+ resolveUserSyncConfig: (userConfigDir: string) => Promise<SyncConfig>;
19
21
  saveManifest: (manifestPath: string, manifest: Manifest) => Promise<void>;
20
22
  scanCanonical: (scopeRoot: string, scope: ConcreteScope) => Promise<CanonicalEntry[]>;
21
- scanBundledManagedCodexAgents: () => Promise<CanonicalEntry[]>;
23
+ scanBundledManagedAgents: () => Promise<CanonicalEntry[]>;
22
24
  getAdapters: () => ProviderAdapter[];
23
25
  getActiveAdapters: (adapters: ProviderAdapter[], scopeRoot: string) => Promise<ProviderAdapter[]>;
24
26
  getSyncMappings: (adapter: ProviderAdapter, scope: Scope) => PathMapping[];
27
+ getAdoptionSources: (adapter: ProviderAdapter, scope: Scope) => AdoptionSource[];
25
28
  detectDrift: (entry: Manifest['entries'][number], scopeRoot: string, copyTransform?: CopyTransform) => Promise<DriftReport>;
26
- detectStrays: (provider: string, providerDir: string, manifest: Manifest, canonicalEntries: CanonicalEntry[], mapping?: Pick<PathMapping, 'contentType' | 'providerExtension'>) => Promise<DriftReport[]>;
29
+ detectStrays: (provider: string, providerDir: string, manifest: Manifest, canonicalEntries: CanonicalEntry[], mapping?: Pick<PathMapping, 'contentType' | 'nativeRead' | 'providerExtension'>) => Promise<DriftReport[]>;
27
30
  detectCodexRoleStrays: (scopeRoot: string, canonicalEntries: CanonicalEntry[], managedRoleNames?: Set<string>) => Promise<CodexRoleStray[]>;
28
31
  computeCodexProjectExtensionPlan: (scopeRoot: string, canonicalEntries: CanonicalEntry[], allowedCanonicalPaths?: string[], options?: {
29
32
  userConfigDir?: string;
30
33
  env?: NodeJS.ProcessEnv;
31
34
  }) => Promise<CodexExtensionPlan>;
32
35
  applyCodexProjectExtensionPlan: (scopeRoot: string, plan: CodexExtensionPlan) => Promise<unknown>;
36
+ computeCursorProjectExtensionPlan: (scopeRoot: string, canonicalEntries: CanonicalEntry[], allowedCanonicalPaths?: string[], options?: {
37
+ userConfigDir?: string;
38
+ env?: NodeJS.ProcessEnv;
39
+ }) => Promise<CursorExtensionPlan>;
40
+ applyCursorProjectExtensionPlan: (scopeRoot: string, plan: CursorExtensionPlan) => Promise<unknown>;
33
41
  selectManyWithAbort: <T extends string>(message: string, choices: MultiSelectChoice<T>[], ctx: PromptContext) => Promise<T[] | null>;
42
+ selectWithAbort: <T extends string>(message: string, choices: SelectChoice<T>[], ctx: PromptContext) => Promise<T | null>;
34
43
  confirmAction: (message: string, ctx: PromptContext) => Promise<boolean>;
35
44
  adoptStray: (scopeRoot: string, stray: StatusStrayCandidate, manifest: Manifest, options?: {
36
45
  replaceCanonical?: boolean;
37
46
  }) => Promise<Manifest>;
47
+ applyCursorSkillDisposition: (scopeRoot: string, stray: StatusStrayCandidate, manifest: Manifest, disposition: CursorSkillDisposition, syncConfigPath: string, options?: {
48
+ replaceCanonical?: boolean;
49
+ }) => Promise<Manifest>;
38
50
  formatStatusTable: (reports: DriftReport[]) => string;
39
51
  }
40
52
  interface StatusStrayCandidate {