@open-agent-toolkit/cli 0.1.45 → 0.1.48

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 (112) hide show
  1. package/assets/agents/oat-phase-implementer.md +202 -191
  2. package/assets/agents/oat-reviewer.md +12 -2
  3. package/assets/config/dispatch-matrix-recommendation.json +120 -13
  4. package/assets/docs/cli-utilities/config-and-local-state.md +7 -0
  5. package/assets/docs/cli-utilities/configuration.md +183 -84
  6. package/assets/docs/cli-utilities/workflow-gates.md +161 -27
  7. package/assets/docs/contributing/skills.md +14 -8
  8. package/assets/docs/provider-sync/config.md +5 -1
  9. package/assets/docs/provider-sync/manifest-and-drift.md +6 -1
  10. package/assets/docs/provider-sync/providers.md +42 -5
  11. package/assets/docs/provider-sync/scope-and-surface.md +3 -2
  12. package/assets/docs/reference/cli-reference.md +3 -1
  13. package/assets/docs/reference/oat-directory-structure.md +27 -26
  14. package/assets/docs/workflows/projects/artifacts.md +31 -1
  15. package/assets/docs/workflows/projects/dispatch-ceiling.md +235 -174
  16. package/assets/docs/workflows/projects/implementation-execution.md +283 -253
  17. package/assets/docs/workflows/projects/lifecycle.md +26 -5
  18. package/assets/docs/workflows/projects/reviews.md +27 -2
  19. package/assets/migration/pjm-restructure.md +1 -1
  20. package/assets/public-package-versions.json +4 -4
  21. package/assets/skills/oat-project-implement/SKILL.md +347 -178
  22. package/assets/skills/oat-project-import-plan/SKILL.md +173 -16
  23. package/assets/skills/oat-project-next/SKILL.md +2 -2
  24. package/assets/skills/oat-project-plan/SKILL.md +122 -92
  25. package/assets/skills/oat-project-plan-writing/SKILL.md +246 -15
  26. package/assets/skills/oat-project-quick-start/SKILL.md +156 -94
  27. package/assets/skills/oat-project-review-provide/SKILL.md +94 -22
  28. package/dist/commands/config/index.d.ts +2 -2
  29. package/dist/commands/config/index.d.ts.map +1 -1
  30. package/dist/commands/config/index.js +129 -53
  31. package/dist/commands/doctor/index.d.ts +2 -2
  32. package/dist/commands/doctor/index.d.ts.map +1 -1
  33. package/dist/commands/doctor/index.js +102 -31
  34. package/dist/commands/gate/index.d.ts +7 -1
  35. package/dist/commands/gate/index.d.ts.map +1 -1
  36. package/dist/commands/gate/index.js +544 -67
  37. package/dist/commands/gate/review-verdict.d.ts +16 -0
  38. package/dist/commands/gate/review-verdict.d.ts.map +1 -1
  39. package/dist/commands/gate/review-verdict.js +72 -9
  40. package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
  41. package/dist/commands/project/dispatch-ceiling/index.js +586 -23
  42. package/dist/commands/providers/codex/index.d.ts +4 -0
  43. package/dist/commands/providers/codex/index.d.ts.map +1 -0
  44. package/dist/commands/providers/codex/index.js +7 -0
  45. package/dist/commands/providers/codex/materialize.d.ts +5 -0
  46. package/dist/commands/providers/codex/materialize.d.ts.map +1 -0
  47. package/dist/commands/providers/codex/materialize.js +157 -0
  48. package/dist/commands/providers/index.d.ts +2 -2
  49. package/dist/commands/providers/index.d.ts.map +1 -1
  50. package/dist/commands/providers/index.js +4 -2
  51. package/dist/commands/providers/providers.types.d.ts +23 -0
  52. package/dist/commands/providers/providers.types.d.ts.map +1 -1
  53. package/dist/commands/shared/codex-strays.d.ts.map +1 -1
  54. package/dist/commands/shared/codex-strays.js +11 -1
  55. package/dist/commands/shared/frontmatter.d.ts +4 -0
  56. package/dist/commands/shared/frontmatter.d.ts.map +1 -1
  57. package/dist/commands/shared/frontmatter.js +23 -0
  58. package/dist/commands/status/index.d.ts +5 -1
  59. package/dist/commands/status/index.d.ts.map +1 -1
  60. package/dist/commands/status/index.js +10 -4
  61. package/dist/commands/sync/index.d.ts.map +1 -1
  62. package/dist/commands/sync/index.js +10 -3
  63. package/dist/commands/sync/sync.types.d.ts +5 -1
  64. package/dist/commands/sync/sync.types.d.ts.map +1 -1
  65. package/dist/config/dispatch-ceiling-preset.d.ts +4 -0
  66. package/dist/config/dispatch-ceiling-preset.d.ts.map +1 -1
  67. package/dist/config/dispatch-ceiling-preset.js +3 -0
  68. package/dist/config/dispatch-policy-options.d.ts +20 -0
  69. package/dist/config/dispatch-policy-options.d.ts.map +1 -0
  70. package/dist/config/dispatch-policy-options.js +96 -0
  71. package/dist/config/oat-config.d.ts +25 -2
  72. package/dist/config/oat-config.d.ts.map +1 -1
  73. package/dist/config/oat-config.js +154 -24
  74. package/dist/config/resolve.d.ts +8 -0
  75. package/dist/config/resolve.d.ts.map +1 -1
  76. package/dist/config/resolve.js +66 -2
  77. package/dist/engine/index.d.ts +1 -1
  78. package/dist/engine/index.d.ts.map +1 -1
  79. package/dist/engine/index.js +1 -1
  80. package/dist/engine/scanner.d.ts +1 -0
  81. package/dist/engine/scanner.d.ts.map +1 -1
  82. package/dist/engine/scanner.js +17 -1
  83. package/dist/fs/paths.d.ts +4 -0
  84. package/dist/fs/paths.d.ts.map +1 -1
  85. package/dist/fs/paths.js +18 -1
  86. package/dist/providers/ceiling/registry.d.ts +8 -5
  87. package/dist/providers/ceiling/registry.d.ts.map +1 -1
  88. package/dist/providers/ceiling/registry.js +25 -7
  89. package/dist/providers/codex/codec/catalog.d.ts +14 -0
  90. package/dist/providers/codex/codec/catalog.d.ts.map +1 -0
  91. package/dist/providers/codex/codec/catalog.js +21 -0
  92. package/dist/providers/codex/codec/config-merge.d.ts +6 -0
  93. package/dist/providers/codex/codec/config-merge.d.ts.map +1 -1
  94. package/dist/providers/codex/codec/config-merge.js +7 -0
  95. package/dist/providers/codex/codec/materialize.d.ts +16 -0
  96. package/dist/providers/codex/codec/materialize.d.ts.map +1 -0
  97. package/dist/providers/codex/codec/materialize.js +58 -0
  98. package/dist/providers/codex/codec/shared.d.ts +20 -0
  99. package/dist/providers/codex/codec/shared.d.ts.map +1 -1
  100. package/dist/providers/codex/codec/shared.js +107 -6
  101. package/dist/providers/codex/codec/sync-extension.d.ts +7 -1
  102. package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
  103. package/dist/providers/codex/codec/sync-extension.js +299 -52
  104. package/dist/providers/identity/availability.d.ts +24 -1
  105. package/dist/providers/identity/availability.d.ts.map +1 -1
  106. package/dist/providers/identity/availability.js +253 -19
  107. package/dist/providers/identity/stamp.d.ts.map +1 -1
  108. package/dist/providers/identity/stamp.js +4 -0
  109. package/dist/shared/types.d.ts +1 -0
  110. package/dist/shared/types.d.ts.map +1 -1
  111. package/dist/shared/types.js +4 -0
  112. package/package.json +2 -2
@@ -1,17 +1,20 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { readFile, rm, writeFile } from 'node:fs/promises';
3
- import { dirname, join, relative, resolve } from 'node:path';
2
+ import { readdir, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { basename, dirname, join, relative, resolve } from 'node:path';
4
4
  import { parseCanonicalAgentFile } from '../../../agents/canonical/index.js';
5
+ import { isCodexMaterializedRouteTarget, isWorkflowDispatchCandidateLadder, isWorkflowDispatchFallbackRoute, normalizeProjectPath, resolveActiveProject, validateDispatchRouteTarget, } from '../../../config/oat-config.js';
6
+ import { resolveEffectiveConfig } from '../../../config/resolve.js';
7
+ import { CliError } from '../../../errors/index.js';
5
8
  import { ensureDir, fileExists } from '../../../fs/io.js';
9
+ import { validateRealPathWithinScope } from '../../../fs/paths.js';
6
10
  import TOML from '@iarna/toml';
11
+ import YAML from 'yaml';
12
+ import { SUPPORTED_CODEX_BASE_ROLES, SUPPORTED_CODEX_ROLE_TARGETS, } from './catalog.js';
7
13
  import { mergeCodexConfig } from './config-merge.js';
8
14
  import { exportCanonicalAgentToCodexRole } from './export-to-codex.js';
9
- import { isOatManagedCodexRoleFile } from './shared.js';
10
- const CODEX_EFFORT_VARIANTS = ['low', 'medium', 'high', 'xhigh'];
11
- const CODEX_EFFORT_VARIANT_BASE_ROLES = new Set([
12
- 'oat-phase-implementer',
13
- 'oat-reviewer',
14
- ]);
15
+ import { materializeCodexRole } from './materialize.js';
16
+ import { isOatManagedCodexRoleFile, readOatManagedCodexRoleOwner, withOatManagedCodexRoleOwner, } from './shared.js';
17
+ const CODEX_MATERIALIZED_BASE_ROLES = new Set(SUPPORTED_CODEX_BASE_ROLES);
15
18
  function hashContent(content) {
16
19
  return createHash('sha256').update(content).digest('hex');
17
20
  }
@@ -42,41 +45,7 @@ function normalizeManagedRolesConfig(desiredRoles) {
42
45
  configFile: role.configFile,
43
46
  }));
44
47
  }
45
- function codexEffortVariantContent(baseContent, baseRoleName, variantRoleName, effort) {
46
- return baseContent
47
- .replace(`# oat-role: ${baseRoleName}`, `# oat-role: ${variantRoleName}`)
48
- .replace('developer_instructions =', `model_reasoning_effort = "${effort}"\ndeveloper_instructions =`);
49
- }
50
- function codexEffortVariantDescription(baseRoleName, effort) {
51
- const roleLabel = baseRoleName === 'oat-reviewer' ? 'reviewer' : 'phase implementer';
52
- if (effort === 'low') {
53
- return `OAT ${roleLabel} pinned to low reasoning effort for narrow mechanical implementation, fix, or review phases.`;
54
- }
55
- if (effort === 'medium') {
56
- return `OAT ${roleLabel} pinned to medium reasoning effort for normal multi-file implementation, fix, or review phases.`;
57
- }
58
- if (effort === 'high') {
59
- return `OAT ${roleLabel} pinned to high reasoning effort for broad, subtle, or higher-risk implementation, fix, or review phases.`;
60
- }
61
- return `OAT ${roleLabel} pinned to xhigh reasoning effort for the highest configured Codex dispatch policy cap.`;
62
- }
63
- function codexEffortVariantsFromBase(exported) {
64
- if (!CODEX_EFFORT_VARIANT_BASE_ROLES.has(exported.roleName)) {
65
- return [];
66
- }
67
- return CODEX_EFFORT_VARIANTS.map((effort) => {
68
- const variantRoleName = `${exported.roleName}-${effort}`;
69
- const configFile = `agents/${variantRoleName}.toml`;
70
- return {
71
- roleName: variantRoleName,
72
- description: codexEffortVariantDescription(exported.roleName, effort),
73
- configFile,
74
- rolePath: join(dirname(exported.rolePath), `${variantRoleName}.toml`),
75
- content: codexEffortVariantContent(exported.content, exported.roleName, variantRoleName, effort),
76
- };
77
- });
78
- }
79
- async function desiredRolesFromCanonical(canonicalEntries, scopeRoot) {
48
+ async function desiredRolesFromCanonical(canonicalEntries, scopeRoot, materializationTargets) {
80
49
  const roles = [];
81
50
  for (const entry of canonicalEntries) {
82
51
  if (entry.type !== 'agent' ||
@@ -93,10 +62,239 @@ async function desiredRolesFromCanonical(canonicalEntries, scopeRoot) {
93
62
  rolePath: join(scopeRoot, '.codex', exported.configFile),
94
63
  content: exported.content,
95
64
  });
96
- roles.push(...codexEffortVariantsFromBase(roles[roles.length - 1]));
65
+ if (CODEX_MATERIALIZED_BASE_ROLES.has(exported.roleName)) {
66
+ for (const target of materializationTargets) {
67
+ const materialized = materializeCodexRole({
68
+ agent: parsed,
69
+ model: target.model,
70
+ effort: target.effort,
71
+ });
72
+ roles.push({
73
+ roleName: materialized.roleName,
74
+ description: materialized.description,
75
+ configFile: materialized.configFile,
76
+ rolePath: join(scopeRoot, '.codex', materialized.configFile),
77
+ content: withOatManagedCodexRoleOwner(materialized.content, target.owner),
78
+ });
79
+ }
80
+ }
81
+ }
82
+ const roleContents = new Map();
83
+ for (const role of roles) {
84
+ const existing = roleContents.get(role.roleName);
85
+ if (existing !== undefined && existing !== role.content) {
86
+ throw new CliError(`Distinct Codex targets produced the same role name ${role.roleName}. Refusing ambiguous role writes.`);
87
+ }
88
+ roleContents.set(role.roleName, role.content);
97
89
  }
98
90
  return roles.sort((left, right) => left.roleName.localeCompare(right.roleName));
99
91
  }
92
+ function collectCodexTargetFromEntry(entry, targets, owner) {
93
+ if (typeof entry === 'string') {
94
+ return;
95
+ }
96
+ if (!isCodexMaterializedRouteTarget('codex', entry)) {
97
+ return;
98
+ }
99
+ const validation = validateDispatchRouteTarget('codex', entry);
100
+ if (!validation.valid || !entry.model || !entry.effort) {
101
+ return;
102
+ }
103
+ const key = `${entry.model}\0${entry.effort}`;
104
+ if (!targets.has(key)) {
105
+ targets.set(key, {
106
+ model: entry.model,
107
+ effort: entry.effort,
108
+ owner,
109
+ });
110
+ }
111
+ }
112
+ function collectCodexTargetsFromCell(cell, targets, owner) {
113
+ if (typeof cell === 'string') {
114
+ return;
115
+ }
116
+ if (isWorkflowDispatchCandidateLadder(cell)) {
117
+ for (const candidate of cell.candidates) {
118
+ if (isWorkflowDispatchFallbackRoute(candidate)) {
119
+ for (const entry of candidate.route) {
120
+ collectCodexTargetFromEntry(entry, targets, owner);
121
+ }
122
+ continue;
123
+ }
124
+ collectCodexTargetFromEntry(candidate, targets, owner);
125
+ }
126
+ return;
127
+ }
128
+ for (const entry of cell) {
129
+ collectCodexTargetFromEntry(entry, targets, owner);
130
+ }
131
+ }
132
+ function collectCodexMaterializationTargetsFromProvider(providerValue, targets, owner) {
133
+ if (providerValue === undefined || typeof providerValue === 'string') {
134
+ return;
135
+ }
136
+ for (const cell of Object.values(providerValue)) {
137
+ if (cell !== undefined) {
138
+ collectCodexTargetsFromCell(cell, targets, owner);
139
+ }
140
+ }
141
+ }
142
+ function sortCodexMaterializationTargets(targets) {
143
+ return [...targets.values()].sort((left, right) => {
144
+ const modelOrder = left.model.localeCompare(right.model);
145
+ return modelOrder === 0
146
+ ? left.effort.localeCompare(right.effort)
147
+ : modelOrder;
148
+ });
149
+ }
150
+ function routeTargetFromUnknown(value) {
151
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
152
+ return null;
153
+ }
154
+ const record = value;
155
+ const target = {};
156
+ for (const key of ['harness', 'model', 'effort']) {
157
+ const rawValue = record[key];
158
+ if (typeof rawValue === 'string' && rawValue.trim()) {
159
+ target[key] = rawValue.trim();
160
+ }
161
+ }
162
+ return Object.keys(target).length > 0 ? target : null;
163
+ }
164
+ function collectCodexTargetsFromUnknownCell(value, targets, owner) {
165
+ const collectCandidate = (candidate) => {
166
+ if (!candidate ||
167
+ typeof candidate !== 'object' ||
168
+ Array.isArray(candidate)) {
169
+ return;
170
+ }
171
+ const route = candidate['route'];
172
+ if (Array.isArray(route)) {
173
+ for (const entry of route) {
174
+ const target = routeTargetFromUnknown(entry);
175
+ if (target) {
176
+ collectCodexTargetFromEntry(target, targets, owner);
177
+ }
178
+ }
179
+ return;
180
+ }
181
+ const target = routeTargetFromUnknown(candidate);
182
+ if (target) {
183
+ collectCodexTargetFromEntry(target, targets, owner);
184
+ }
185
+ };
186
+ if (Array.isArray(value)) {
187
+ for (const entry of value) {
188
+ collectCandidate(entry);
189
+ }
190
+ return;
191
+ }
192
+ if (!value || typeof value !== 'object') {
193
+ return;
194
+ }
195
+ const candidates = value['candidates'];
196
+ if (!Array.isArray(candidates)) {
197
+ return;
198
+ }
199
+ for (const entry of candidates) {
200
+ if (typeof entry === 'string') {
201
+ continue;
202
+ }
203
+ collectCandidate(entry);
204
+ }
205
+ }
206
+ function collectCodexTargetsFromUnknownProvider(value, targets, owner) {
207
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
208
+ return;
209
+ }
210
+ for (const cell of Object.values(value)) {
211
+ collectCodexTargetsFromUnknownCell(cell, targets, owner);
212
+ }
213
+ }
214
+ function frontmatterBlock(content) {
215
+ const normalized = content.startsWith('\uFEFF') ? content.slice(1) : content;
216
+ if (!normalized.startsWith('---\n')) {
217
+ return null;
218
+ }
219
+ const end = normalized.indexOf('\n---', 4);
220
+ return end > 0 ? normalized.slice(4, end) : null;
221
+ }
222
+ async function collectProjectStateCodexTargets(scopeRoot, options, targets) {
223
+ const projectPathCandidate = options.projectPath === undefined
224
+ ? (await resolveActiveProject(scopeRoot)).path
225
+ : options.projectPath;
226
+ if (!projectPathCandidate) {
227
+ return;
228
+ }
229
+ const projectPath = normalizeProjectPath(scopeRoot, projectPathCandidate);
230
+ if (!projectPath) {
231
+ if (options.projectPath !== undefined) {
232
+ throw new CliError(`Project-scoped Codex materialization path must be repo-relative or inside repo root: ${projectPathCandidate}`);
233
+ }
234
+ return;
235
+ }
236
+ let realProjectPath;
237
+ try {
238
+ const validated = await validateRealPathWithinScope(join(scopeRoot, projectPath), scopeRoot);
239
+ realProjectPath = validated.realPath;
240
+ }
241
+ catch {
242
+ if (options.projectPath !== undefined) {
243
+ throw new CliError(`Project-scoped Codex materialization path must be repo-relative or inside repo root: ${projectPathCandidate}`);
244
+ }
245
+ return;
246
+ }
247
+ const statePath = join(realProjectPath, 'state.md');
248
+ const stateContent = await readOptionalFile(statePath);
249
+ if (!stateContent) {
250
+ return;
251
+ }
252
+ const frontmatter = frontmatterBlock(stateContent);
253
+ if (!frontmatter) {
254
+ return;
255
+ }
256
+ const parsed = YAML.parse(frontmatter);
257
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
258
+ return;
259
+ }
260
+ const policy = parsed['oat_dispatch_policy'];
261
+ if (!policy || typeof policy !== 'object' || Array.isArray(policy)) {
262
+ return;
263
+ }
264
+ const matrix = policy['matrix'];
265
+ if (!matrix || typeof matrix !== 'object' || Array.isArray(matrix)) {
266
+ return;
267
+ }
268
+ collectCodexTargetsFromUnknownProvider(matrix['codex'], targets, 'project-config');
269
+ }
270
+ function isUserCodexScope(scopeRoot, options) {
271
+ if (!options.userConfigDir) {
272
+ return false;
273
+ }
274
+ return resolve(scopeRoot) === resolve(options.userConfigDir, '..');
275
+ }
276
+ async function readCodexMaterializationTargets(scopeRoot, options = {}) {
277
+ const targets = new Map();
278
+ const effectiveConfig = await resolveEffectiveConfig(scopeRoot, options.userConfigDir ?? join(scopeRoot, '.oat', '__no-user-config__'), options.env);
279
+ if (isUserCodexScope(scopeRoot, options)) {
280
+ collectCodexMaterializationTargetsFromProvider(effectiveConfig.user.workflow?.dispatchCeiling?.providers?.codex, targets, 'user-config');
281
+ return sortCodexMaterializationTargets(targets);
282
+ }
283
+ for (const target of SUPPORTED_CODEX_ROLE_TARGETS) {
284
+ targets.set(`${target.model}\0${target.effort}`, {
285
+ ...target,
286
+ owner: 'supported-catalogue',
287
+ });
288
+ }
289
+ for (const providerValue of [
290
+ effectiveConfig.shared.workflow?.dispatchCeiling?.providers?.codex,
291
+ effectiveConfig.local.workflow?.dispatchCeiling?.providers?.codex,
292
+ ]) {
293
+ collectCodexMaterializationTargetsFromProvider(providerValue, targets, 'project-config');
294
+ }
295
+ await collectProjectStateCodexTargets(scopeRoot, options, targets);
296
+ return sortCodexMaterializationTargets(targets);
297
+ }
100
298
  function parseConfigAgentTable(content) {
101
299
  if (!content || content.trim() === '') {
102
300
  return {};
@@ -122,9 +320,9 @@ function parseConfigAgentTable(content) {
122
320
  return {};
123
321
  }
124
322
  }
125
- async function collectStaleManagedRoles(scopeRoot, existingConfigContent, desiredRoleNames) {
323
+ async function collectStaleManagedRoles(scopeRoot, existingConfigContent, desiredRoleNames, cleanupOwner, removeLegacyProjectRoles) {
126
324
  const agents = parseConfigAgentTable(existingConfigContent);
127
- const stale = [];
325
+ const stale = new Set();
128
326
  for (const [roleName, roleConfig] of Object.entries(agents)) {
129
327
  if (desiredRoleNames.has(roleName)) {
130
328
  continue;
@@ -138,19 +336,68 @@ async function collectStaleManagedRoles(scopeRoot, existingConfigContent, desire
138
336
  if (!roleContent || !isOatManagedCodexRoleFile(roleContent, roleName)) {
139
337
  continue;
140
338
  }
141
- stale.push(roleName);
339
+ const owner = readOatManagedCodexRoleOwner(roleContent);
340
+ const legacyProjectRole = removeLegacyProjectRoles &&
341
+ owner === null &&
342
+ SUPPORTED_CODEX_BASE_ROLES.some((baseRole) => roleName.startsWith(`${baseRole}-`));
343
+ if (owner !== cleanupOwner && !legacyProjectRole) {
344
+ continue;
345
+ }
346
+ stale.add(roleName);
347
+ }
348
+ const agentsDir = join(scopeRoot, '.codex', 'agents');
349
+ let roleFiles;
350
+ try {
351
+ roleFiles = await readdir(agentsDir);
352
+ }
353
+ catch {
354
+ roleFiles = [];
355
+ }
356
+ for (const roleFile of roleFiles) {
357
+ if (!roleFile.endsWith('.toml')) {
358
+ continue;
359
+ }
360
+ const roleName = basename(roleFile, '.toml');
361
+ if (desiredRoleNames.has(roleName) || stale.has(roleName)) {
362
+ continue;
363
+ }
364
+ const rolePath = join(agentsDir, roleFile);
365
+ const roleContent = await readOptionalFile(rolePath);
366
+ if (!roleContent || !isOatManagedCodexRoleFile(roleContent, roleName)) {
367
+ continue;
368
+ }
369
+ const owner = readOatManagedCodexRoleOwner(roleContent);
370
+ const legacyProjectRole = removeLegacyProjectRoles &&
371
+ owner === null &&
372
+ SUPPORTED_CODEX_BASE_ROLES.some((baseRole) => roleName.startsWith(`${baseRole}-`));
373
+ if (owner !== cleanupOwner && !legacyProjectRole) {
374
+ continue;
375
+ }
376
+ stale.add(roleName);
142
377
  }
143
- return stale.sort((left, right) => left.localeCompare(right));
378
+ return [...stale].sort((left, right) => left.localeCompare(right));
144
379
  }
145
- export async function computeCodexProjectExtensionPlan(scopeRoot, canonicalEntries, allowedCanonicalPaths) {
380
+ export async function computeCodexProjectExtensionPlan(scopeRoot, canonicalEntries, allowedCanonicalPaths, options = {}) {
146
381
  const isPartialSync = allowedCanonicalPaths !== undefined && allowedCanonicalPaths.length > 0;
147
- const desiredRoles = await desiredRolesFromCanonical(canonicalEntries.filter((entry) => canonicalPathAllowed(scopeRoot, entry, allowedCanonicalPaths)), scopeRoot);
382
+ const materializationTargets = await readCodexMaterializationTargets(scopeRoot, options);
383
+ if (!isPartialSync &&
384
+ isUserCodexScope(scopeRoot, options) &&
385
+ materializationTargets.length > 0) {
386
+ const canonicalBaseRoles = new Set(canonicalEntries
387
+ .filter((entry) => entry.type === 'agent' && entry.isFile)
388
+ .map((entry) => entry.name.replace(/\.md$/i, '')));
389
+ const missingBaseRoles = SUPPORTED_CODEX_BASE_ROLES.filter((role) => !canonicalBaseRoles.has(role));
390
+ if (missingBaseRoles.length > 0) {
391
+ throw new CliError(`Bundled managed Codex role definitions are unavailable for user sync: ${missingBaseRoles.join(', ')}. Refusing stale user-role cleanup.`);
392
+ }
393
+ }
394
+ const desiredRoles = await desiredRolesFromCanonical(canonicalEntries.filter((entry) => canonicalPathAllowed(scopeRoot, entry, allowedCanonicalPaths)), scopeRoot, materializationTargets);
148
395
  const desiredRoleNames = new Set(desiredRoles.map((role) => role.roleName));
149
396
  const existingConfigPath = configPath(scopeRoot);
150
397
  const existingConfigContent = await readOptionalFile(existingConfigPath);
151
398
  const staleRoles = isPartialSync
152
399
  ? []
153
- : await collectStaleManagedRoles(scopeRoot, existingConfigContent, desiredRoleNames);
400
+ : await collectStaleManagedRoles(scopeRoot, existingConfigContent, desiredRoleNames, isUserCodexScope(scopeRoot, options) ? 'user-config' : 'project-config', !isUserCodexScope(scopeRoot, options));
154
401
  if (isPartialSync && desiredRoles.length === 0) {
155
402
  return {
156
403
  operations: [],
@@ -227,7 +474,7 @@ export async function computeCodexProjectExtensionPlan(scopeRoot, canonicalEntri
227
474
  });
228
475
  return {
229
476
  operations,
230
- managedRoles: desiredRoles.map((role) => role.roleName),
477
+ managedRoles: [...desiredRoles.map((role) => role.roleName), ...staleRoles],
231
478
  aggregateConfigHash: hashContent(configMerge.mergedContent),
232
479
  };
233
480
  }
@@ -1,4 +1,10 @@
1
1
  export type MatrixCellAvailability = 'valid' | 'unknown-value' | 'unvalidated';
2
+ export interface MatrixCellAvailabilityResult {
3
+ availability: MatrixCellAvailability;
4
+ allowedValues?: string[];
5
+ message?: string;
6
+ }
7
+ export type MatrixCellAvailabilityResponse = MatrixCellAvailability | MatrixCellAvailabilityResult;
2
8
  export interface CursorAgentRunOptions {
3
9
  cwd: string;
4
10
  env: NodeJS.ProcessEnv;
@@ -8,15 +14,32 @@ export interface CursorAgentRunResult {
8
14
  stdout: string;
9
15
  stderr: string;
10
16
  }
17
+ export interface CodexRunOptions {
18
+ cwd: string;
19
+ env: NodeJS.ProcessEnv;
20
+ }
21
+ export interface CodexRunResult {
22
+ ok: boolean;
23
+ stdout: string;
24
+ stderr: string;
25
+ }
11
26
  export interface AvailabilityOracleDependencies {
12
27
  pathExists: (path: string) => Promise<boolean>;
13
28
  runCursorAgent: (args: string[], options: CursorAgentRunOptions) => Promise<CursorAgentRunResult>;
29
+ runCodex: (args: string[], options: CodexRunOptions) => Promise<CodexRunResult>;
14
30
  env?: NodeJS.ProcessEnv;
15
31
  }
16
32
  export interface ValidateMatrixCellOptions {
17
33
  cwd: string;
34
+ detailed?: boolean;
18
35
  env?: NodeJS.ProcessEnv;
19
36
  dependencies?: Partial<AvailabilityOracleDependencies>;
37
+ target?: {
38
+ model?: string;
39
+ effort?: string;
40
+ } | null;
20
41
  }
21
- export declare function validateMatrixCell(provider: string, value: string, options: ValidateMatrixCellOptions): Promise<MatrixCellAvailability>;
42
+ export declare function normalizeMatrixCellAvailability(result: MatrixCellAvailabilityResponse): MatrixCellAvailabilityResult;
43
+ export declare function validateCursorSubagentModel(value: string, options: ValidateMatrixCellOptions): Promise<MatrixCellAvailabilityResult>;
44
+ export declare function validateMatrixCell(provider: string, value: string, options: ValidateMatrixCellOptions): Promise<MatrixCellAvailabilityResponse>;
22
45
  //# sourceMappingURL=availability.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"availability.d.ts","sourceRoot":"","sources":["../../../src/providers/identity/availability.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,eAAe,GAAG,aAAa,CAAC;AAE/E,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,8BAA8B;IAC7C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,cAAc,EAAE,CACd,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,qBAAqB,KAC3B,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC,8BAA8B,CAAC,CAAC;CACxD;AAwID,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,sBAAsB,CAAC,CA8BjC"}
1
+ {"version":3,"file":"availability.d.ts","sourceRoot":"","sources":["../../../src/providers/identity/availability.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,eAAe,GAAG,aAAa,CAAC;AAE/E,MAAM,WAAW,4BAA4B;IAC3C,YAAY,EAAE,sBAAsB,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,8BAA8B,GACtC,sBAAsB,GACtB,4BAA4B,CAAC;AAEjC,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,8BAA8B;IAC7C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,cAAc,EAAE,CACd,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,qBAAqB,KAC3B,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACnC,QAAQ,EAAE,CACR,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,eAAe,KACrB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC,8BAA8B,CAAC,CAAC;IACvD,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,IAAI,CAAC;CACV;AAWD,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,8BAA8B,GACrC,4BAA4B,CAE9B;AA+VD,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,4BAA4B,CAAC,CAsEvC;AAED,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,8BAA8B,CAAC,CA2CzC"}