@open-agent-toolkit/cli 0.3.37 → 0.3.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/assets/bundle-metadata.json +1 -1
- package/assets/docs/getting-started/bootstrap.md +15 -16
- package/assets/docs/getting-started/tool-packs.md +15 -14
- package/assets/docs/provider-sync/commands.md +6 -5
- package/assets/docs/provider-sync/manifest-and-drift.md +26 -19
- package/assets/docs/provider-sync/pilot-with-a-team.md +17 -17
- package/assets/docs/provider-sync/providers.md +4 -4
- package/assets/docs/provider-sync/scope-and-surface.md +5 -5
- package/assets/docs/reference/cli-reference.md +8 -5
- package/assets/docs/reference/oat-directory-structure.md +1 -1
- package/assets/docs/reference/project-artifacts.md +12 -3
- package/assets/docs/reference/troubleshooting.md +4 -5
- package/assets/docs/reference/what-oat-writes.md +54 -59
- package/assets/docs/skills/repo-improve.md +23 -19
- package/assets/docs/workflows/advanced/workflow-gates.md +7 -1
- package/assets/docs/workflows/projects/closeout/closeout-skills.md +9 -6
- package/assets/docs/workflows/projects/execution/execution-skills.md +1 -2
- package/assets/docs/workflows/projects/execution/implementation-execution.md +3 -2
- package/assets/docs/workflows/projects/lifecycle.md +3 -1
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-project-brief/SKILL.md +1 -1
- package/assets/skills/oat-project-brief/references/brief-contract.md +32 -9
- package/assets/skills/oat-project-complete/SKILL.md +17 -7
- package/assets/skills/oat-project-implement/SKILL.md +2 -2
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +14 -4
- package/assets/skills/oat-project-pr-final/SKILL.md +2 -2
- package/assets/skills/oat-project-pr-progress/SKILL.md +23 -12
- package/assets/skills/oat-repo-knowledge-index/SKILL.md +226 -131
- package/dist/commands/doctor/index.d.ts.map +1 -1
- package/dist/commands/doctor/index.js +5 -2
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +10 -5
- package/dist/commands/init/tools/index.d.ts.map +1 -1
- package/dist/commands/init/tools/index.js +106 -28
- package/dist/commands/init/tools/project-guidance.js +1 -1
- package/dist/commands/project/closeout-check/index.d.ts.map +1 -1
- package/dist/commands/project/closeout-check/index.js +4 -8
- package/dist/commands/project/delivery/check.d.ts.map +1 -1
- package/dist/commands/project/delivery/check.js +32 -14
- package/dist/commands/project/delivery/evidence.d.ts +39 -0
- package/dist/commands/project/delivery/evidence.d.ts.map +1 -1
- package/dist/commands/project/delivery/evidence.js +163 -17
- package/dist/commands/project/delivery/refresh.d.ts.map +1 -1
- package/dist/commands/project/delivery/refresh.js +13 -16
- package/dist/commands/project/journal-check/index.d.ts +2 -0
- package/dist/commands/project/journal-check/index.d.ts.map +1 -1
- package/dist/commands/project/journal-check/index.js +78 -1
- package/dist/commands/project/journal-check/types.d.ts +1 -1
- package/dist/commands/project/journal-check/types.d.ts.map +1 -1
- package/dist/commands/project/new/index.d.ts.map +1 -1
- package/dist/commands/project/new/index.js +1 -0
- package/dist/commands/project/new/scaffold.d.ts +1 -1
- package/dist/commands/project/new/scaffold.d.ts.map +1 -1
- package/dist/commands/project/new/scaffold.js +1 -1
- package/dist/commands/providers/codex/materialize.d.ts +5 -1
- package/dist/commands/providers/codex/materialize.d.ts.map +1 -1
- package/dist/commands/providers/codex/materialize.js +6 -1
- package/dist/commands/shared/exact-path-commit.d.ts.map +1 -1
- package/dist/commands/shared/exact-path-commit.js +11 -1
- package/dist/commands/shared/scope-option.d.ts +3 -1
- package/dist/commands/shared/scope-option.d.ts.map +1 -1
- package/dist/commands/shared/scope-option.js +4 -2
- package/dist/commands/status/index.d.ts.map +1 -1
- package/dist/commands/status/index.js +4 -1
- package/dist/commands/tools/install/index.d.ts.map +1 -1
- package/dist/commands/tools/install/index.js +1 -1
- package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts +11 -0
- package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-lifecycle-outcome.js +16 -0
- package/dist/config/oat-config.d.ts +6 -1
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +14 -22
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/providers/codex/codec/config-merge.d.ts +19 -0
- package/dist/providers/codex/codec/config-merge.d.ts.map +1 -1
- package/dist/providers/codex/codec/config-merge.js +75 -8
- package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
- package/dist/providers/codex/codec/sync-extension.js +84 -38
- package/dist/providers/cursor/codec/materialize.d.ts.map +1 -1
- package/dist/providers/cursor/codec/materialize.js +17 -1
- package/package.json +3 -3
|
@@ -12,7 +12,7 @@ import TOML from '@iarna/toml';
|
|
|
12
12
|
import { hasMaterializationChanges, summarizeMaterializationPlan, } from '../../shared/index.js';
|
|
13
13
|
import YAML from 'yaml';
|
|
14
14
|
import { SUPPORTED_CODEX_BASE_ROLES, SUPPORTED_CODEX_ROLE_TARGETS, } from './catalog.js';
|
|
15
|
-
import { mergeCodexConfig, readCodexMaxDepth, } from './config-merge.js';
|
|
15
|
+
import { describeCodexConfigMerge, mergeCodexConfig, readCodexMaxDepth, } from './config-merge.js';
|
|
16
16
|
import { exportCanonicalAgentToCodexRole } from './export-to-codex.js';
|
|
17
17
|
import { materializeCodexRole } from './materialize.js';
|
|
18
18
|
import { isOatManagedCodexRoleFile, readOatManagedCodexRoleOwner, withOatManagedCodexRoleOwner, } from './shared.js';
|
|
@@ -35,6 +35,13 @@ async function readOptionalFile(path) {
|
|
|
35
35
|
}
|
|
36
36
|
return readFile(path, 'utf8');
|
|
37
37
|
}
|
|
38
|
+
function describeConfigOperation(summary, configMerge) {
|
|
39
|
+
const details = describeCodexConfigMerge(configMerge);
|
|
40
|
+
if (configMerge.removedRoles.length > 0) {
|
|
41
|
+
details.unshift(`removes stale role tables: ${configMerge.removedRoles.map((role) => `agents.${role}`).join(', ')}`);
|
|
42
|
+
}
|
|
43
|
+
return [summary, ...details].join('; ');
|
|
44
|
+
}
|
|
38
45
|
function normalizeManagedRolesConfig(desiredRoles) {
|
|
39
46
|
return desiredRoles.map((role) => ({
|
|
40
47
|
roleName: role.roleName,
|
|
@@ -42,7 +49,7 @@ function normalizeManagedRolesConfig(desiredRoles) {
|
|
|
42
49
|
configFile: role.configFile,
|
|
43
50
|
}));
|
|
44
51
|
}
|
|
45
|
-
async function desiredRolesFromCanonical(canonicalEntries, scopeRoot, materializationTargets) {
|
|
52
|
+
async function desiredRolesFromCanonical(canonicalEntries, scopeRoot, materializationTargets, scopeOwner) {
|
|
46
53
|
const roles = [];
|
|
47
54
|
for (const entry of canonicalEntries) {
|
|
48
55
|
if (entry.type !== 'agent' ||
|
|
@@ -57,7 +64,7 @@ async function desiredRolesFromCanonical(canonicalEntries, scopeRoot, materializ
|
|
|
57
64
|
description: exported.description,
|
|
58
65
|
configFile: exported.configFile,
|
|
59
66
|
rolePath: join(scopeRoot, '.codex', exported.configFile),
|
|
60
|
-
content: exported.content,
|
|
67
|
+
content: withOatManagedCodexRoleOwner(exported.content, scopeOwner),
|
|
61
68
|
sourcePath: entry.canonicalPath,
|
|
62
69
|
});
|
|
63
70
|
if (CODEX_MATERIALIZED_BASE_ROLES.has(exported.roleName)) {
|
|
@@ -329,9 +336,47 @@ function parseConfigAgentTable(content) {
|
|
|
329
336
|
return {};
|
|
330
337
|
}
|
|
331
338
|
}
|
|
332
|
-
|
|
339
|
+
function codexScopeOwner(scopeRoot, options) {
|
|
340
|
+
return isUserCodexScope(scopeRoot, options)
|
|
341
|
+
? 'user-config'
|
|
342
|
+
: 'project-config';
|
|
343
|
+
}
|
|
344
|
+
function codexCleanupOwners(scopeRoot, options) {
|
|
345
|
+
return isUserCodexScope(scopeRoot, options)
|
|
346
|
+
? ['user-config']
|
|
347
|
+
: ['supported-catalogue', 'project-config'];
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Whether a partial sync removes a canonical agent. A removed file can no
|
|
351
|
+
* longer be parsed for the role name it declared, so such a sync runs the full
|
|
352
|
+
* stale cleanup against the roles every present canonical agent produces.
|
|
353
|
+
*/
|
|
354
|
+
function removesCanonicalAgent(canonicalEntries, allowedCanonicalPaths) {
|
|
355
|
+
const presentFiles = new Set(canonicalEntries
|
|
356
|
+
.filter((entry) => entry.type === 'agent' && entry.isFile)
|
|
357
|
+
.map((entry) => entry.name));
|
|
358
|
+
return allowedCanonicalPaths.some((path) => {
|
|
359
|
+
const match = /^\.agents\/agents\/([^/]+\.md)$/.exec(path);
|
|
360
|
+
return match?.[1] !== undefined && !presentFiles.has(match[1]);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
async function collectStaleManagedRoles(scopeRoot, existingConfigContent, desiredRoleNames, cleanupOwners, removeLegacyProjectRoles) {
|
|
333
364
|
const agents = parseConfigAgentTable(existingConfigContent);
|
|
334
|
-
const
|
|
365
|
+
const staleFiles = new Set();
|
|
366
|
+
const staleTables = new Set();
|
|
367
|
+
const isStaleManagedRole = (roleName, roleContent) => {
|
|
368
|
+
if (!roleContent || !isOatManagedCodexRoleFile(roleContent, roleName)) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
const owner = readOatManagedCodexRoleOwner(roleContent);
|
|
372
|
+
const legacyProjectRole = removeLegacyProjectRoles &&
|
|
373
|
+
owner === null &&
|
|
374
|
+
SUPPORTED_CODEX_BASE_ROLES.some((baseRole) => roleName.startsWith(`${baseRole}-`));
|
|
375
|
+
return ((owner !== null && cleanupOwners.includes(owner)) || legacyProjectRole);
|
|
376
|
+
};
|
|
377
|
+
// Stale cleanup deletes files, so a candidate whose real path (through a
|
|
378
|
+
// symlinked file or `.codex/agents` directory) leaves the scope is skipped.
|
|
379
|
+
const isWithinScope = (rolePath) => validateRealPathWithinScope(rolePath, scopeRoot).then(() => true, () => false);
|
|
335
380
|
for (const [roleName, roleConfig] of Object.entries(agents)) {
|
|
336
381
|
if (desiredRoleNames.has(roleName)) {
|
|
337
382
|
continue;
|
|
@@ -341,18 +386,12 @@ async function collectStaleManagedRoles(scopeRoot, existingConfigContent, desire
|
|
|
341
386
|
continue;
|
|
342
387
|
}
|
|
343
388
|
const rolePath = join(scopeRoot, '.codex', configFile);
|
|
344
|
-
|
|
345
|
-
|
|
389
|
+
if (!(await isWithinScope(rolePath)) ||
|
|
390
|
+
!isStaleManagedRole(roleName, await readOptionalFile(rolePath))) {
|
|
346
391
|
continue;
|
|
347
392
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
owner === null &&
|
|
351
|
-
SUPPORTED_CODEX_BASE_ROLES.some((baseRole) => roleName.startsWith(`${baseRole}-`));
|
|
352
|
-
if (owner !== cleanupOwner && !legacyProjectRole) {
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
stale.add(roleName);
|
|
393
|
+
staleTables.add(roleName);
|
|
394
|
+
staleFiles.add(roleName);
|
|
356
395
|
}
|
|
357
396
|
const agentsDir = join(scopeRoot, '.codex', 'agents');
|
|
358
397
|
let roleFiles;
|
|
@@ -367,28 +406,27 @@ async function collectStaleManagedRoles(scopeRoot, existingConfigContent, desire
|
|
|
367
406
|
continue;
|
|
368
407
|
}
|
|
369
408
|
const roleName = basename(roleFile, '.toml');
|
|
370
|
-
if (desiredRoleNames.has(roleName) ||
|
|
409
|
+
if (desiredRoleNames.has(roleName) || staleFiles.has(roleName)) {
|
|
371
410
|
continue;
|
|
372
411
|
}
|
|
373
412
|
const rolePath = join(agentsDir, roleFile);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
const owner = readOatManagedCodexRoleOwner(roleContent);
|
|
379
|
-
const legacyProjectRole = removeLegacyProjectRoles &&
|
|
380
|
-
owner === null &&
|
|
381
|
-
SUPPORTED_CODEX_BASE_ROLES.some((baseRole) => roleName.startsWith(`${baseRole}-`));
|
|
382
|
-
if (owner !== cleanupOwner && !legacyProjectRole) {
|
|
413
|
+
if (!(await isWithinScope(rolePath)) ||
|
|
414
|
+
!isStaleManagedRole(roleName, await readOptionalFile(rolePath))) {
|
|
383
415
|
continue;
|
|
384
416
|
}
|
|
385
|
-
|
|
417
|
+
// A table of the same name that points elsewhere was re-pointed by the
|
|
418
|
+
// user; only the leftover file is OAT's to remove.
|
|
419
|
+
staleFiles.add(roleName);
|
|
386
420
|
}
|
|
387
|
-
|
|
421
|
+
const sorted = (names) => [...names].sort((left, right) => left.localeCompare(right));
|
|
422
|
+
return { roleFiles: sorted(staleFiles), configTables: sorted(staleTables) };
|
|
388
423
|
}
|
|
389
424
|
export async function computeCodexProjectExtensionPlan(scopeRoot, canonicalEntries, allowedCanonicalPaths, options = {}) {
|
|
390
425
|
const isPartialSync = allowedCanonicalPaths !== undefined && allowedCanonicalPaths.length > 0;
|
|
426
|
+
const cleansStaleRoles = !isPartialSync ||
|
|
427
|
+
removesCanonicalAgent(canonicalEntries, allowedCanonicalPaths);
|
|
391
428
|
const materializationTargets = await readCodexMaterializationTargets(scopeRoot, options);
|
|
429
|
+
const scopeOwner = codexScopeOwner(scopeRoot, options);
|
|
392
430
|
if (!isPartialSync &&
|
|
393
431
|
isUserCodexScope(scopeRoot, options) &&
|
|
394
432
|
materializationTargets.length > 0) {
|
|
@@ -400,11 +438,20 @@ export async function computeCodexProjectExtensionPlan(scopeRoot, canonicalEntri
|
|
|
400
438
|
throw new CliError(`Bundled managed Codex role definitions are unavailable for user sync: ${missingBaseRoles.join(', ')}. Refusing stale user-role cleanup.`);
|
|
401
439
|
}
|
|
402
440
|
}
|
|
403
|
-
const desiredRoles = await desiredRolesFromCanonical(canonicalEntries.filter((entry) => canonicalPathAllowed(scopeRoot, entry, allowedCanonicalPaths)), scopeRoot, materializationTargets);
|
|
441
|
+
const desiredRoles = await desiredRolesFromCanonical(canonicalEntries.filter((entry) => canonicalPathAllowed(scopeRoot, entry, allowedCanonicalPaths)), scopeRoot, materializationTargets, scopeOwner);
|
|
404
442
|
const desiredRoleNames = new Set(desiredRoles.map((role) => role.roleName));
|
|
405
443
|
const existingConfigPath = configPath(scopeRoot);
|
|
406
444
|
const existingConfigContent = await readOptionalFile(existingConfigPath);
|
|
407
|
-
|
|
445
|
+
const producedRoleNames = isPartialSync && cleansStaleRoles
|
|
446
|
+
? new Set((await desiredRolesFromCanonical(canonicalEntries, scopeRoot, materializationTargets, scopeOwner)).map((role) => role.roleName))
|
|
447
|
+
: desiredRoleNames;
|
|
448
|
+
const staleRoles = cleansStaleRoles
|
|
449
|
+
? await collectStaleManagedRoles(scopeRoot, existingConfigContent, producedRoleNames, codexCleanupOwners(scopeRoot, options), !isUserCodexScope(scopeRoot, options))
|
|
450
|
+
: { roleFiles: [], configTables: [] };
|
|
451
|
+
if (isPartialSync &&
|
|
452
|
+
desiredRoles.length === 0 &&
|
|
453
|
+
staleRoles.roleFiles.length === 0 &&
|
|
454
|
+
staleRoles.configTables.length === 0) {
|
|
408
455
|
const aggregateConfigHash = hashContent(existingConfigContent ?? '');
|
|
409
456
|
return {
|
|
410
457
|
provider: 'codex',
|
|
@@ -420,9 +467,6 @@ export async function computeCodexProjectExtensionPlan(scopeRoot, canonicalEntri
|
|
|
420
467
|
};
|
|
421
468
|
}
|
|
422
469
|
const inheritedMaxDepth = await readInheritedCodexMaxDepth(scopeRoot, options);
|
|
423
|
-
const staleRoles = isPartialSync
|
|
424
|
-
? []
|
|
425
|
-
: await collectStaleManagedRoles(scopeRoot, existingConfigContent, desiredRoleNames, isUserCodexScope(scopeRoot, options) ? 'user-config' : 'project-config', !isUserCodexScope(scopeRoot, options));
|
|
426
470
|
const operations = [];
|
|
427
471
|
for (const role of desiredRoles) {
|
|
428
472
|
const existingRoleContent = await readOptionalFile(role.rolePath);
|
|
@@ -462,10 +506,12 @@ export async function computeCodexProjectExtensionPlan(scopeRoot, canonicalEntri
|
|
|
462
506
|
roleName: role.roleName,
|
|
463
507
|
});
|
|
464
508
|
}
|
|
465
|
-
for (const staleRole of staleRoles) {
|
|
509
|
+
for (const staleRole of staleRoles.roleFiles) {
|
|
466
510
|
const staleRolePath = join(scopeRoot, '.codex', 'agents', `${staleRole}.toml`);
|
|
467
511
|
const staleRoleContent = await readOptionalFile(staleRolePath);
|
|
468
|
-
|
|
512
|
+
const staleRoleWithinScope = await validateRealPathWithinScope(staleRolePath, scopeRoot).then(() => true, () => false);
|
|
513
|
+
if (staleRoleWithinScope &&
|
|
514
|
+
staleRoleContent &&
|
|
469
515
|
isOatManagedCodexRoleFile(staleRoleContent, staleRole)) {
|
|
470
516
|
operations.push({
|
|
471
517
|
provider: 'codex',
|
|
@@ -481,7 +527,7 @@ export async function computeCodexProjectExtensionPlan(scopeRoot, canonicalEntri
|
|
|
481
527
|
const configMerge = mergeCodexConfig({
|
|
482
528
|
existingContent: existingConfigContent,
|
|
483
529
|
desiredRoles: normalizeManagedRolesConfig(desiredRoles),
|
|
484
|
-
staleManagedRoles: staleRoles,
|
|
530
|
+
staleManagedRoles: staleRoles.configTables,
|
|
485
531
|
inheritedMaxDepth,
|
|
486
532
|
});
|
|
487
533
|
operations.push({
|
|
@@ -493,16 +539,16 @@ export async function computeCodexProjectExtensionPlan(scopeRoot, canonicalEntri
|
|
|
493
539
|
: 'skip',
|
|
494
540
|
target: 'config',
|
|
495
541
|
path: '.codex/config.toml',
|
|
496
|
-
reason: existingConfigContent === null
|
|
542
|
+
reason: describeConfigOperation(existingConfigContent === null
|
|
497
543
|
? 'codex config missing'
|
|
498
544
|
: configMerge.changed
|
|
499
545
|
? 'codex config differs from desired managed state'
|
|
500
|
-
: 'codex config already in sync',
|
|
546
|
+
: 'codex config already in sync', configMerge),
|
|
501
547
|
content: configMerge.mergedContent,
|
|
502
548
|
});
|
|
503
549
|
const managedRoles = [
|
|
504
550
|
...desiredRoles.map((role) => role.roleName),
|
|
505
|
-
...staleRoles,
|
|
551
|
+
...staleRoles.roleFiles,
|
|
506
552
|
];
|
|
507
553
|
const aggregateConfigHash = hashContent(configMerge.mergedContent);
|
|
508
554
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"materialize.d.ts","sourceRoot":"","sources":["../../../../src/providers/cursor/codec/materialize.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAKhE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,UAAU,CAAC;AAQlB,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,qBAAqB,CAAC;CAChC;AAED,MAAM,WAAW,6BAA6B;IAC5C,KAAK,EAAE,sBAAsB,CAAC;IAC9B,OAAO,EAAE,qBAAqB,CAAC;IAC/B,KAAK,EAAE,eAAe,CAAC;CACxB;AAWD,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,OAAO,EACP,KAAK,GACN,EAAE,6BAA6B,GAAG,uBAAuB,CA8BzD;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACjC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAC1C,KAAK,EAAE,eAAe,CAAC;CACxB,GAAG,uBAAuB,EAAE,CAwB5B;
|
|
1
|
+
{"version":3,"file":"materialize.d.ts","sourceRoot":"","sources":["../../../../src/providers/cursor/codec/materialize.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAKhE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,UAAU,CAAC;AAQlB,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,eAAe,CAAC;IACvB,OAAO,EAAE,qBAAqB,CAAC;CAChC;AAED,MAAM,WAAW,6BAA6B;IAC5C,KAAK,EAAE,sBAAsB,CAAC;IAC9B,OAAO,EAAE,qBAAqB,CAAC;IAC/B,KAAK,EAAE,eAAe,CAAC;CACxB;AAWD,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,OAAO,EACP,KAAK,GACN,EAAE,6BAA6B,GAAG,uBAAuB,CA8BzD;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,sBAAsB,EAAE,CAAC;IACjC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAC1C,KAAK,EAAE,eAAe,CAAC;CACxB,GAAG,uBAAuB,EAAE,CAwB5B;AAgDD,wBAAsB,sCAAsC,CAC1D,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC,CA+Ef"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { lstat, readFile, readdir } from 'node:fs/promises';
|
|
1
|
+
import { lstat, readFile, readdir, stat } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { CliError } from '../../../errors/index.js';
|
|
4
4
|
import { validateRealPathWithinScope } from '../../../fs/paths.js';
|
|
@@ -70,6 +70,17 @@ async function readPathStats(path) {
|
|
|
70
70
|
throw error;
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
async function isDanglingLink(path) {
|
|
74
|
+
try {
|
|
75
|
+
await stat(path);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
// Other failures, such as a link loop, fall through to the scope check
|
|
80
|
+
// and its controlled refusal.
|
|
81
|
+
return error.code === 'ENOENT';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
73
84
|
function readDeclaredCursorAgentName(content) {
|
|
74
85
|
const normalized = content.startsWith('\uFEFF') ? content.slice(1) : content;
|
|
75
86
|
const match = /^---\n([\s\S]*?)\n---(?:\n|$)/.exec(normalized);
|
|
@@ -118,6 +129,11 @@ export async function assertNoUnmanagedCursorAgentCollisions(scopeRoot, desiredR
|
|
|
118
129
|
continue;
|
|
119
130
|
}
|
|
120
131
|
if (fileStats.isSymbolicLink()) {
|
|
132
|
+
// A dangling link cannot define a role, and the main sync plan
|
|
133
|
+
// removes the manifest-tracked view it was created as.
|
|
134
|
+
if (await isDanglingLink(absolutePath)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
121
137
|
try {
|
|
122
138
|
await validateRealPathWithinScope(absolutePath, scopeRoot);
|
|
123
139
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-agent-toolkit/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.39",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Open Agent Toolkit CLI",
|
|
6
6
|
"homepage": "https://github.com/voxmedia/open-agent-toolkit/tree/main/packages/cli",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"unified": "11.0.5",
|
|
42
42
|
"yaml": "2.8.2",
|
|
43
43
|
"zod": "^3.25.76",
|
|
44
|
-
"@open-agent-toolkit/control-plane": "0.3.
|
|
45
|
-
"@open-agent-toolkit/docs-transforms": "0.3.
|
|
44
|
+
"@open-agent-toolkit/control-plane": "0.3.39",
|
|
45
|
+
"@open-agent-toolkit/docs-transforms": "0.3.39"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/node": "^22.10.0",
|