contextos-agents 2.1.1 → 2.3.0
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/.agents/AGENTS.md +53 -396
- package/.agents/adapters/aider/export.js +11 -16
- package/.agents/adapters/claude/export.js +13 -13
- package/.agents/adapters/copilot/export.js +29 -8
- package/.agents/adapters/cursor/export.js +9 -18
- package/.agents/adapters/gemini/export.js +11 -46
- package/.agents/adapters/pure-compiler.js +65 -42
- package/.agents/adapters/shared.js +35 -1
- package/.agents/adapters/zed/export.js +2 -2
- package/.agents/compiled/registry.v2.json +30 -18
- package/.agents/compiled/registry.v2.sha256 +1 -1
- package/.agents/compiler/manifest-compiler.js +5 -29
- package/.agents/core/skills/context-os/references/project-graph.md +3 -3
- package/.agents/core/skills/engineering-workflow/SKILL.md +11 -316
- package/.agents/core/skills/engineering-workflow/references/workflow.md +336 -0
- package/.agents/core/skills/engineering-workflow/skill.yaml +2 -4
- package/.agents/core/skills/gstack-roles/SKILL.md +11 -128
- package/.agents/core/skills/gstack-roles/references/roles.md +149 -0
- package/.agents/core/skills/gstack-roles/skill.yaml +2 -4
- package/.agents/core/skills/ponytail-mindset/SKILL.md +13 -165
- package/.agents/core/skills/ponytail-mindset/references/minimalism.md +186 -0
- package/.agents/core/skills/ponytail-mindset/skill.yaml +2 -5
- package/.agents/core/skills/security/skill.yaml +1 -0
- package/.agents/ctx.js +22 -17
- package/.agents/customization-dx.js +13 -9
- package/.agents/doctor.js +2 -2
- package/.agents/generated/claude/skills/context-manager/EXAMPLES.md +19 -0
- package/.agents/generated/claude/skills/context-manager/SKILL.md +0 -29
- package/.agents/generated/claude/skills/context-manager/TROUBLESHOOTING.md +7 -0
- package/.agents/generated/claude/skills/context-manager/VALIDATION.json +12 -0
- package/.agents/generated/claude/skills/context-manager/references/context-rules.md +59 -0
- package/.agents/generated/claude/skills/context-os/EXAMPLES.md +21 -0
- package/.agents/generated/claude/skills/context-os/SKILL.md +0 -31
- package/.agents/generated/claude/skills/context-os/TROUBLESHOOTING.md +7 -0
- package/.agents/generated/claude/skills/context-os/VALIDATION.json +12 -0
- package/.agents/generated/claude/skills/context-os/packs.yaml +59 -0
- package/.agents/generated/claude/skills/context-os/references/context-rules.md +68 -0
- package/.agents/generated/claude/skills/context-os/references/pipeline.md +119 -0
- package/.agents/generated/claude/skills/context-os/references/project-graph.md +103 -0
- package/.agents/generated/claude/skills/context-os/rules.yaml +135 -0
- package/.agents/generated/claude/skills/engineering-workflow/EXAMPLES.md +57 -0
- package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +10 -391
- package/.agents/generated/claude/skills/engineering-workflow/TROUBLESHOOTING.md +19 -0
- package/.agents/generated/claude/skills/engineering-workflow/VALIDATION.json +12 -0
- package/.agents/generated/claude/skills/engineering-workflow/references/workflow.md +336 -0
- package/.agents/generated/claude/skills/gemini-precision/EXAMPLES.md +72 -0
- package/.agents/generated/claude/skills/gemini-precision/SKILL.md +0 -100
- package/.agents/generated/claude/skills/gemini-precision/TROUBLESHOOTING.md +25 -0
- package/.agents/generated/claude/skills/gemini-precision/VALIDATION.json +12 -0
- package/.agents/generated/claude/skills/gstack-roles/EXAMPLES.md +23 -0
- package/.agents/generated/claude/skills/gstack-roles/SKILL.md +10 -164
- package/.agents/generated/claude/skills/gstack-roles/TROUBLESHOOTING.md +13 -0
- package/.agents/generated/claude/skills/gstack-roles/VALIDATION.json +12 -0
- package/.agents/generated/claude/skills/gstack-roles/references/roles.md +149 -0
- package/.agents/generated/claude/skills/ponytail-mindset/EXAMPLES.md +45 -0
- package/.agents/generated/claude/skills/ponytail-mindset/SKILL.md +12 -228
- package/.agents/generated/claude/skills/ponytail-mindset/TROUBLESHOOTING.md +19 -0
- package/.agents/generated/claude/skills/ponytail-mindset/VALIDATION.json +12 -0
- package/.agents/generated/claude/skills/ponytail-mindset/references/minimalism.md +186 -0
- package/.agents/generated/claude/skills/security/EXAMPLES.md +64 -0
- package/.agents/generated/claude/skills/security/SKILL.md +0 -86
- package/.agents/generated/claude/skills/security/TROUBLESHOOTING.md +19 -0
- package/.agents/generated/claude/skills/security/VALIDATION.json +12 -0
- package/.agents/generated/gemini/skills/context-manager/EXAMPLES.md +19 -0
- package/.agents/generated/gemini/skills/context-manager/SKILL.md +1 -33
- package/.agents/generated/gemini/skills/context-manager/TROUBLESHOOTING.md +7 -0
- package/.agents/generated/gemini/skills/context-manager/VALIDATION.json +12 -0
- package/.agents/generated/gemini/skills/context-manager/references/context-rules.md +59 -0
- package/.agents/generated/gemini/skills/context-os/EXAMPLES.md +21 -0
- package/.agents/generated/gemini/skills/context-os/SKILL.md +0 -35
- package/.agents/generated/gemini/skills/context-os/TROUBLESHOOTING.md +7 -0
- package/.agents/generated/gemini/skills/context-os/VALIDATION.json +12 -0
- package/.agents/generated/gemini/skills/context-os/packs.yaml +59 -0
- package/.agents/generated/gemini/skills/context-os/references/context-rules.md +68 -0
- package/.agents/generated/gemini/skills/context-os/references/pipeline.md +119 -0
- package/.agents/generated/gemini/skills/context-os/references/project-graph.md +103 -0
- package/.agents/generated/gemini/skills/context-os/rules.yaml +135 -0
- package/.agents/generated/gemini/skills/engineering-workflow/EXAMPLES.md +57 -0
- package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +11 -396
- package/.agents/generated/gemini/skills/engineering-workflow/TROUBLESHOOTING.md +19 -0
- package/.agents/generated/gemini/skills/engineering-workflow/VALIDATION.json +12 -0
- package/.agents/generated/gemini/skills/engineering-workflow/references/workflow.md +336 -0
- package/.agents/generated/gemini/skills/gemini-precision/EXAMPLES.md +72 -0
- package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +0 -104
- package/.agents/generated/gemini/skills/gemini-precision/TROUBLESHOOTING.md +25 -0
- package/.agents/generated/gemini/skills/gemini-precision/VALIDATION.json +12 -0
- package/.agents/generated/gemini/skills/gstack-roles/EXAMPLES.md +23 -0
- package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +11 -169
- package/.agents/generated/gemini/skills/gstack-roles/TROUBLESHOOTING.md +13 -0
- package/.agents/generated/gemini/skills/gstack-roles/VALIDATION.json +12 -0
- package/.agents/generated/gemini/skills/gstack-roles/references/roles.md +149 -0
- package/.agents/generated/gemini/skills/ponytail-mindset/EXAMPLES.md +45 -0
- package/.agents/generated/gemini/skills/ponytail-mindset/SKILL.md +13 -233
- package/.agents/generated/gemini/skills/ponytail-mindset/TROUBLESHOOTING.md +19 -0
- package/.agents/generated/gemini/skills/ponytail-mindset/VALIDATION.json +12 -0
- package/.agents/generated/gemini/skills/ponytail-mindset/references/minimalism.md +186 -0
- package/.agents/generated/gemini/skills/security/EXAMPLES.md +64 -0
- package/.agents/generated/gemini/skills/security/SKILL.md +2 -92
- package/.agents/generated/gemini/skills/security/TROUBLESHOOTING.md +19 -0
- package/.agents/generated/gemini/skills/security/VALIDATION.json +12 -0
- package/.agents/plugins.js +272 -28
- package/.agents/resolver/canonical-resolver.js +43 -7
- package/.agents/resolver/resolve-args.js +31 -0
- package/.agents/stats.js +8 -11
- package/.agents/workspace/workspace-graph.js +16 -6
- package/README.md +61 -6
- package/bin/index.js +157 -51
- package/bin/lib/ui.js +140 -0
- package/package.json +5 -2
package/.agents/plugins.js
CHANGED
|
@@ -47,6 +47,25 @@ const REGISTRY_URL = 'https://raw.githubusercontent.com/kok-o/contextos-agents
|
|
|
47
47
|
const MAX_DOWNLOAD_BYTES = 5 * 1024 * 1024;
|
|
48
48
|
const SAFE_SKILL_NAME = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
|
|
49
49
|
|
|
50
|
+
function getCatalogDir() {
|
|
51
|
+
if (process.env.CONTEXTOS_CATALOG_DIR && fs.existsSync(process.env.CONTEXTOS_CATALOG_DIR)) {
|
|
52
|
+
return process.env.CONTEXTOS_CATALOG_DIR;
|
|
53
|
+
}
|
|
54
|
+
const localAgentsCatalog = path.join(__dirname, 'catalog', 'skills');
|
|
55
|
+
if (fs.existsSync(localAgentsCatalog)) return localAgentsCatalog;
|
|
56
|
+
|
|
57
|
+
const relativeCatalog = path.join(path.resolve(__dirname, '..'), 'catalog', 'skills');
|
|
58
|
+
if (fs.existsSync(relativeCatalog)) return relativeCatalog;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const pkgPath = require.resolve('contextos-agents/package.json', { paths: [__dirname, process.cwd()] });
|
|
62
|
+
const pkgCatalog = path.join(path.dirname(pkgPath), 'catalog', 'skills');
|
|
63
|
+
if (fs.existsSync(pkgCatalog)) return pkgCatalog;
|
|
64
|
+
} catch {}
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
50
69
|
// ── ANSI helpers ──────────────────────────────────────────────────────────────
|
|
51
70
|
const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
|
|
52
71
|
const c = {
|
|
@@ -291,7 +310,7 @@ function parseRef(ref, projectRoot = null) {
|
|
|
291
310
|
|
|
292
311
|
// 1. Built-in package catalog skills take strict precedence for catalog names.
|
|
293
312
|
// Consumer project local files can never spoof or hijack package catalog skills.
|
|
294
|
-
const pkgCatalogPath = path.join(
|
|
313
|
+
const pkgCatalogPath = path.join(getCatalogDir(), ref);
|
|
295
314
|
if (fs.existsSync(pkgCatalogPath) && fs.existsSync(path.join(pkgCatalogPath, 'SKILL.md'))) {
|
|
296
315
|
return {
|
|
297
316
|
type: 'catalog',
|
|
@@ -636,9 +655,137 @@ function isSafeSkillName(skillName) {
|
|
|
636
655
|
return typeof skillName === 'string' && SAFE_SKILL_NAME.test(skillName);
|
|
637
656
|
}
|
|
638
657
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
658
|
+
function findClosestSkill(target, candidates) {
|
|
659
|
+
let closest = null;
|
|
660
|
+
let minDistance = Infinity;
|
|
661
|
+
for (const item of candidates) {
|
|
662
|
+
if (item === target) return item;
|
|
663
|
+
if (item.includes(target) || target.includes(item)) return item;
|
|
664
|
+
const dist = levenshtein(target, item);
|
|
665
|
+
if (dist < minDistance && dist <= 3) {
|
|
666
|
+
minDistance = dist;
|
|
667
|
+
closest = item;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return closest;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function levenshtein(a, b) {
|
|
674
|
+
const m = a.length, n = b.length;
|
|
675
|
+
const d = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
676
|
+
for (let i = 0; i <= m; i++) d[i][0] = i;
|
|
677
|
+
for (let j = 0; j <= n; j++) d[0][j] = j;
|
|
678
|
+
for (let i = 1; i <= m; i++) {
|
|
679
|
+
for (let j = 1; j <= n; j++) {
|
|
680
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
681
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return d[m][n];
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Batch install all available catalog skills.
|
|
689
|
+
*/
|
|
690
|
+
async function addAllFromCatalog(options = {}) {
|
|
691
|
+
const dryRun = Boolean(options.dryRun || options['dry-run']);
|
|
692
|
+
const force = Boolean(options.force);
|
|
693
|
+
const catalogDir = getCatalogDir();
|
|
694
|
+
if (!fs.existsSync(catalogDir)) {
|
|
695
|
+
throw new Error('Catalog directory not found in package');
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const catalogSkills = fs.readdirSync(catalogDir).filter(n => {
|
|
699
|
+
const full = path.join(catalogDir, n);
|
|
700
|
+
return fs.statSync(full).isDirectory() && fs.existsSync(path.join(full, 'SKILL.md'));
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
console.log('');
|
|
704
|
+
console.log(c.bold(`Batch installing ${catalogSkills.length} catalog skills...`));
|
|
705
|
+
|
|
706
|
+
const lock = readLock();
|
|
707
|
+
const existingNames = new Set(lock.plugins.map(p => p.name));
|
|
708
|
+
|
|
709
|
+
const projectLock = new ProjectMutationLock(ROOT);
|
|
710
|
+
const tx = new JournaledTransaction(ROOT);
|
|
711
|
+
let lockToken = null;
|
|
712
|
+
|
|
713
|
+
let installedCount = 0;
|
|
714
|
+
let skippedCount = 0;
|
|
715
|
+
const newlyInstalled = [];
|
|
716
|
+
|
|
717
|
+
try {
|
|
718
|
+
lockToken = projectLock.acquire({ command: 'skill:add-all' });
|
|
719
|
+
|
|
720
|
+
for (const skillName of catalogSkills) {
|
|
721
|
+
if (existingNames.has(skillName) && !force) {
|
|
722
|
+
skippedCount++;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const descriptor = {
|
|
727
|
+
type: 'catalog',
|
|
728
|
+
name: skillName,
|
|
729
|
+
path: path.join(catalogDir, skillName),
|
|
730
|
+
isPinned: true,
|
|
731
|
+
raw: skillName,
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
const installedSha256 = installFromCatalog(descriptor, skillName, dryRun, null, false, tx);
|
|
735
|
+
|
|
736
|
+
if (!dryRun) {
|
|
737
|
+
const idx = lock.plugins.findIndex(p => p.name === skillName);
|
|
738
|
+
const entry = {
|
|
739
|
+
name: skillName,
|
|
740
|
+
ref: skillName,
|
|
741
|
+
type: 'catalog',
|
|
742
|
+
sha256: installedSha256,
|
|
743
|
+
installedAt: new Date().toISOString(),
|
|
744
|
+
};
|
|
745
|
+
if (idx >= 0) lock.plugins[idx] = entry;
|
|
746
|
+
else lock.plugins.push(entry);
|
|
747
|
+
newlyInstalled.push(skillName);
|
|
748
|
+
}
|
|
749
|
+
installedCount++;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (!dryRun) {
|
|
753
|
+
writeLock(lock, tx);
|
|
754
|
+
tx.commit();
|
|
755
|
+
|
|
756
|
+
for (const skillName of newlyInstalled) {
|
|
757
|
+
const targetDir = path.join(PLUGINS_DIR, skillName);
|
|
758
|
+
if (fs.existsSync(targetDir)) {
|
|
759
|
+
const digestResult = calculateTreeDigest(targetDir);
|
|
760
|
+
const entry = lock.plugins.find(p => p.name === skillName);
|
|
761
|
+
if (entry) entry.treeDigest = digestResult.treeDigest;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
writeLock(lock);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
console.log('');
|
|
768
|
+
if (dryRun) {
|
|
769
|
+
console.log(c.dim(`[DRY-RUN] Would install ${installedCount} skills (${skippedCount} already installed - skipped).`));
|
|
770
|
+
} else {
|
|
771
|
+
console.log(c.green(c.bold(`✓ Batch install complete: ${installedCount} catalog skills installed.`)));
|
|
772
|
+
if (skippedCount > 0) {
|
|
773
|
+
console.log(c.dim(` (${skippedCount} skills already installed - skipped)`));
|
|
774
|
+
}
|
|
775
|
+
console.log('');
|
|
776
|
+
console.log(' Next steps:');
|
|
777
|
+
console.log(' node .agents/ctx.js export all # recompile with new skills');
|
|
778
|
+
console.log(' node .agents/ctx.js validate # verify consistency');
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
return { installedCount, skippedCount };
|
|
782
|
+
} catch (err) {
|
|
783
|
+
console.error(c.red(`\n[ERROR] Batch installation failed: ${err.message}`));
|
|
784
|
+
process.exit(1);
|
|
785
|
+
} finally {
|
|
786
|
+
if (lockToken) projectLock.release(lockToken);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
642
789
|
|
|
643
790
|
/**
|
|
644
791
|
* skill add <ref> [--dry-run] [--checksum <sha256>] [--force-unsafe-prompts]
|
|
@@ -650,6 +797,10 @@ async function add(ref, options = {}) {
|
|
|
650
797
|
const allowFloating = Boolean(options.allowFloating || options['allow-floating']);
|
|
651
798
|
const force = Boolean(options.force);
|
|
652
799
|
|
|
800
|
+
if (ref === '--all' || ref === 'all' || options.all) {
|
|
801
|
+
return await addAllFromCatalog(options);
|
|
802
|
+
}
|
|
803
|
+
|
|
653
804
|
if (!ref) {
|
|
654
805
|
console.error(c.red('Usage: ctx.js skill add <ref> [--checksum <sha256>]'));
|
|
655
806
|
console.error(' ref can be: username/repo, username/repo/path/to/skill, or npm-package-name');
|
|
@@ -663,7 +814,37 @@ async function add(ref, options = {}) {
|
|
|
663
814
|
throw new Error(`Invalid plugin name derived from reference: '${skillName}'`);
|
|
664
815
|
}
|
|
665
816
|
|
|
666
|
-
//
|
|
817
|
+
// 1. Check for conflict with builtin skills first
|
|
818
|
+
const builtinPath = path.join(CORE_SKILLS, skillName);
|
|
819
|
+
if (fs.existsSync(builtinPath)) {
|
|
820
|
+
console.error(c.red(`\n[ERROR] '${skillName}' conflicts with a built-in skill.`));
|
|
821
|
+
console.error(c.red(' Built-in skills cannot be overridden via --add-skill.'));
|
|
822
|
+
console.error(c.dim(` To customize a built-in skill for your project, run: contextos skill override ${skillName}`));
|
|
823
|
+
process.exit(1);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// 2. If single word not in catalog, check for typo suggestion
|
|
827
|
+
if (descriptor.type !== 'catalog' && !ref.includes('/') && !ref.includes('@')) {
|
|
828
|
+
const catalogDir = getCatalogDir();
|
|
829
|
+
if (fs.existsSync(catalogDir)) {
|
|
830
|
+
const catalogSkills = fs.readdirSync(catalogDir).filter(n => {
|
|
831
|
+
try {
|
|
832
|
+
return fs.statSync(path.join(catalogDir, n)).isDirectory();
|
|
833
|
+
} catch {
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
const suggestion = findClosestSkill(skillName, catalogSkills);
|
|
838
|
+
if (suggestion) {
|
|
839
|
+
console.error(c.red(`\n[ERROR] Skill '${skillName}' not found in catalog.`));
|
|
840
|
+
console.log(c.yellow(` Did you mean: "${suggestion}"?`));
|
|
841
|
+
console.log(c.dim(' Run "contextos skill list --available" to see all catalog skills.'));
|
|
842
|
+
process.exit(1);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// 3. Supply-chain source pinning check (Wave 6)
|
|
667
848
|
if (!dryRun && descriptor.type !== 'catalog') {
|
|
668
849
|
const pinning = validatePluginPinning(
|
|
669
850
|
{
|
|
@@ -688,15 +869,6 @@ async function add(ref, options = {}) {
|
|
|
688
869
|
console.log(c.bold(`Installing skill: ${c.cyan(skillName)}`));
|
|
689
870
|
console.log(c.dim(` Source: ${ref} (${descriptor.type})`));
|
|
690
871
|
|
|
691
|
-
// Check for conflict with builtin skills
|
|
692
|
-
const builtinPath = path.join(CORE_SKILLS, skillName);
|
|
693
|
-
if (fs.existsSync(builtinPath)) {
|
|
694
|
-
console.error(c.red(`\n[ERROR] '${skillName}' conflicts with a built-in skill.`));
|
|
695
|
-
console.error(c.red(' Built-in skills cannot be overridden via --add-skill.'));
|
|
696
|
-
console.error(c.dim(' To customize a built-in skill, fork the repo and submit a PR.'));
|
|
697
|
-
process.exit(1);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
872
|
// Check if already installed
|
|
701
873
|
const lock = readLock();
|
|
702
874
|
const existing = lock.plugins.find(p => p.name === skillName);
|
|
@@ -779,6 +951,21 @@ async function add(ref, options = {}) {
|
|
|
779
951
|
// We don't need to do anything since it wasn't committed
|
|
780
952
|
}
|
|
781
953
|
console.error(c.red(`\n[ERROR] Failed to install '${skillName}': ${err.message}`));
|
|
954
|
+
const catalogDir = getCatalogDir();
|
|
955
|
+
if (fs.existsSync(catalogDir)) {
|
|
956
|
+
const catalogSkills = fs.readdirSync(catalogDir).filter(n => {
|
|
957
|
+
try {
|
|
958
|
+
return fs.statSync(path.join(catalogDir, n)).isDirectory();
|
|
959
|
+
} catch {
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
const suggestion = findClosestSkill(skillName, catalogSkills);
|
|
964
|
+
if (suggestion) {
|
|
965
|
+
console.log(c.yellow(` Did you mean: "${suggestion}"?`));
|
|
966
|
+
console.log(c.dim(' Run "contextos skill list --available" to see all catalog skills.'));
|
|
967
|
+
}
|
|
968
|
+
}
|
|
782
969
|
process.exit(1);
|
|
783
970
|
} finally {
|
|
784
971
|
if (lockToken) projectLock.release(lockToken);
|
|
@@ -898,25 +1085,46 @@ function remove(skillName) {
|
|
|
898
1085
|
}
|
|
899
1086
|
|
|
900
1087
|
/**
|
|
901
|
-
* skill list
|
|
1088
|
+
* skill list [options]
|
|
902
1089
|
*/
|
|
903
|
-
function list() {
|
|
1090
|
+
function list(options = {}) {
|
|
1091
|
+
const showAvailable = Boolean(options.available || options['--available'] || options.all);
|
|
904
1092
|
const lock = readLock();
|
|
905
1093
|
|
|
906
1094
|
// Builtin skills
|
|
907
1095
|
const builtins = fs.existsSync(CORE_SKILLS)
|
|
908
|
-
? fs.readdirSync(CORE_SKILLS).filter(n =>
|
|
1096
|
+
? fs.readdirSync(CORE_SKILLS).filter(n => {
|
|
1097
|
+
try {
|
|
1098
|
+
return fs.statSync(path.join(CORE_SKILLS, n)).isDirectory();
|
|
1099
|
+
} catch {
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
})
|
|
909
1103
|
: [];
|
|
910
1104
|
|
|
911
1105
|
// Plugin skills
|
|
912
1106
|
const plugins = lock.plugins;
|
|
913
1107
|
|
|
1108
|
+
// Catalog skills
|
|
1109
|
+
const catalogDir = getCatalogDir();
|
|
1110
|
+
const catalogSkills = fs.existsSync(catalogDir)
|
|
1111
|
+
? fs.readdirSync(catalogDir).filter(n => {
|
|
1112
|
+
try {
|
|
1113
|
+
return fs.statSync(path.join(catalogDir, n)).isDirectory();
|
|
1114
|
+
} catch {
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1117
|
+
})
|
|
1118
|
+
: [];
|
|
1119
|
+
const installedNames = new Set(plugins.map(p => p.name));
|
|
1120
|
+
const availableCatalog = catalogSkills.filter(n => !installedNames.has(n));
|
|
1121
|
+
|
|
914
1122
|
console.log('');
|
|
915
1123
|
console.log(c.bold('ContextOS Skills'));
|
|
916
1124
|
console.log('');
|
|
917
1125
|
|
|
918
1126
|
// Built-ins
|
|
919
|
-
console.log(c.bold(` Built-in (${builtins.length})`));
|
|
1127
|
+
console.log(c.bold(` Built-in Core (${builtins.length})`));
|
|
920
1128
|
for (const name of builtins) {
|
|
921
1129
|
console.log(` ${c.cyan('●')} ${name} ${c.dim('(core)')}`);
|
|
922
1130
|
}
|
|
@@ -924,22 +1132,39 @@ function list() {
|
|
|
924
1132
|
// Plugins
|
|
925
1133
|
console.log('');
|
|
926
1134
|
if (plugins.length === 0) {
|
|
927
|
-
console.log(c.bold(' Plugins (0)'));
|
|
928
|
-
console.log(c.dim(' No plugins installed yet.'));
|
|
929
|
-
console.log(c.dim(' Install one:
|
|
1135
|
+
console.log(c.bold(' Installed Plugins & Domain Skills (0)'));
|
|
1136
|
+
console.log(c.dim(' No domain skills or plugins installed yet.'));
|
|
1137
|
+
console.log(c.dim(' Install one: contextos skill add <name> (or --all)'));
|
|
930
1138
|
} else {
|
|
931
|
-
console.log(c.bold(` Plugins (${plugins.length})`));
|
|
1139
|
+
console.log(c.bold(` Installed Plugins & Domain Skills (${plugins.length})`));
|
|
932
1140
|
for (const p of plugins) {
|
|
933
1141
|
const when = p.installedAt ? new Date(p.installedAt).toLocaleDateString() : '?';
|
|
934
1142
|
console.log(` ${c.green('●')} ${p.name} ${c.dim(`← ${p.ref} (installed ${when})`)}`);
|
|
935
1143
|
}
|
|
936
1144
|
}
|
|
937
1145
|
|
|
1146
|
+
// Available in Catalog
|
|
1147
|
+
console.log('');
|
|
1148
|
+
if (showAvailable || availableCatalog.length > 0) {
|
|
1149
|
+
console.log(c.bold(` Available in Catalog (${availableCatalog.length})`));
|
|
1150
|
+
if (showAvailable) {
|
|
1151
|
+
for (const name of availableCatalog) {
|
|
1152
|
+
console.log(` ${c.dim('○')} ${name}`);
|
|
1153
|
+
}
|
|
1154
|
+
console.log('');
|
|
1155
|
+
console.log(c.dim(' Install with: contextos skill add <name> (or contextos skill add --all)'));
|
|
1156
|
+
} else {
|
|
1157
|
+
console.log(c.dim(` ${availableCatalog.length} domain skills available in local catalog.`));
|
|
1158
|
+
console.log(c.dim(' Run "contextos skill list --available" to see all, or install with:'));
|
|
1159
|
+
console.log(c.dim(' contextos skill add <name> | contextos skill add --all'));
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
938
1163
|
console.log('');
|
|
939
1164
|
}
|
|
940
1165
|
|
|
941
1166
|
/**
|
|
942
|
-
* skill search <query>
|
|
1167
|
+
* skill search <query> - searches the hosted registry.json
|
|
943
1168
|
*/
|
|
944
1169
|
async function search(query) {
|
|
945
1170
|
console.log(c.dim(`\nFetching registry from: ${REGISTRY_URL}\n`));
|
|
@@ -996,11 +1221,11 @@ async function search(query) {
|
|
|
996
1221
|
* both core (builtin) and plugins.
|
|
997
1222
|
* Adapters call this instead of reading CORE_SKILLS directly.
|
|
998
1223
|
*/
|
|
999
|
-
function collectAllSkillDirs(targetRoot) {
|
|
1224
|
+
function collectAllSkillDirs(targetRoot, options = {}) {
|
|
1000
1225
|
const isTargetExplicit = Boolean(targetRoot);
|
|
1001
1226
|
const root = targetRoot || process.cwd();
|
|
1002
|
-
const agentsDir = path.join(root, '.agents');
|
|
1003
|
-
const localCore = path.join(agentsDir, 'core', 'skills');
|
|
1227
|
+
const agentsDir = options.agentsDir || path.join(root, '.agents');
|
|
1228
|
+
const localCore = options.coreSkillsDir || path.join(agentsDir, 'core', 'skills');
|
|
1004
1229
|
const localPlugins = path.join(agentsDir, 'plugins');
|
|
1005
1230
|
|
|
1006
1231
|
const coreDir = fs.existsSync(localCore) ? localCore : (isTargetExplicit ? null : CORE_SKILLS);
|
|
@@ -1027,13 +1252,32 @@ function collectAllSkillDirs(targetRoot) {
|
|
|
1027
1252
|
const subDir = path.join(nestedSkills, sub);
|
|
1028
1253
|
if (fs.statSync(subDir).isDirectory()) dirs.push(subDir);
|
|
1029
1254
|
}
|
|
1030
|
-
} else if (fs.existsSync(path.join(d, 'SKILL.md')) || fs.existsSync(path.join(d, 'skill.yaml'))) {
|
|
1255
|
+
} else if (fs.existsSync(path.join(d, 'SKILL.md')) || fs.existsSync(path.join(d, 'skill.yaml')) || fs.existsSync(path.join(d, 'skill.v2.yaml'))) {
|
|
1031
1256
|
dirs.push(d);
|
|
1032
1257
|
}
|
|
1033
1258
|
}
|
|
1034
1259
|
}
|
|
1035
1260
|
|
|
1036
|
-
|
|
1261
|
+
// Higher ownership tiers replace an upstream skill with the same ID. Preserve
|
|
1262
|
+
// upstream duplicates so the compiler can still diagnose conflicting installs.
|
|
1263
|
+
for (const tier of ['vendor', 'project']) {
|
|
1264
|
+
const tierRoot = path.join(agentsDir, tier, 'skills');
|
|
1265
|
+
if (!fs.existsSync(tierRoot)) continue;
|
|
1266
|
+
for (const name of fs.readdirSync(tierRoot).sort()) {
|
|
1267
|
+
const dir = path.join(tierRoot, name);
|
|
1268
|
+
const { resolvedPath } = require('./filesystem/index.js').resolveManagedPath(root, path.relative(root, dir));
|
|
1269
|
+
if (!fs.statSync(resolvedPath).isDirectory()) continue;
|
|
1270
|
+
if (!fs.existsSync(path.join(dir, 'SKILL.md')) && !fs.existsSync(path.join(dir, 'skill.yaml')) && !fs.existsSync(path.join(dir, 'skill.v2.yaml'))) continue;
|
|
1271
|
+
for (let i = dirs.length - 1; i >= 0; i--) {
|
|
1272
|
+
if (path.basename(dirs[i]) === name) dirs.splice(i, 1);
|
|
1273
|
+
}
|
|
1274
|
+
dirs.push(dir);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
if (isTargetExplicit) {
|
|
1278
|
+
for (const dir of dirs) require('./filesystem/index.js').resolveManagedPath(root, path.relative(root, dir));
|
|
1279
|
+
}
|
|
1280
|
+
return dirs.sort();
|
|
1037
1281
|
}
|
|
1038
1282
|
|
|
1039
1283
|
module.exports = {
|
|
@@ -650,6 +650,12 @@ class CanonicalResolver {
|
|
|
650
650
|
}
|
|
651
651
|
}
|
|
652
652
|
|
|
653
|
+
if (resolvedId === 'security' && ['keyword', 'file_glob', 'explicit', 'safety_required'].includes(kind)) {
|
|
654
|
+
if (profileExcluded.delete(resolvedId)) {
|
|
655
|
+
warnings.push({ code: 'CTX_REQUIRED_SECURITY_PROFILE_CONFLICT', message: 'Task safety guidance overrides the profile exclusion of security.' });
|
|
656
|
+
}
|
|
657
|
+
kind = 'safety_required';
|
|
658
|
+
}
|
|
653
659
|
if (profileExcluded.has(resolvedId)) return;
|
|
654
660
|
if (!evidenceBySkill.has(resolvedId)) evidenceBySkill.set(resolvedId, []);
|
|
655
661
|
evidenceBySkill.get(resolvedId).push({ kind, weight, reason });
|
|
@@ -836,6 +842,10 @@ class CanonicalResolver {
|
|
|
836
842
|
}
|
|
837
843
|
}
|
|
838
844
|
|
|
845
|
+
if (risk.value === 'high' || risk.value === 'destructive') {
|
|
846
|
+
addEvidence('security', 'safety_required', 100, `Required safety guidance for ${risk.value} risk task`);
|
|
847
|
+
}
|
|
848
|
+
|
|
839
849
|
// 3. Compute Candidate Scores & Intent Precedence
|
|
840
850
|
const candidates = [];
|
|
841
851
|
for (const [id, evList] of evidenceBySkill.entries()) {
|
|
@@ -850,7 +860,7 @@ class CanonicalResolver {
|
|
|
850
860
|
if ((e.kind === 'keyword' || e.kind === 'alias') && e.weight >= 20) {
|
|
851
861
|
hasDirectTaskIntent = true;
|
|
852
862
|
}
|
|
853
|
-
if (e.kind === 'explicit' || e.kind === 'profile_required') {
|
|
863
|
+
if (e.kind === 'explicit' || e.kind === 'profile_required' || e.kind === 'safety_required') {
|
|
854
864
|
isMandatory = true;
|
|
855
865
|
hasDirectTaskIntent = true;
|
|
856
866
|
}
|
|
@@ -929,7 +939,7 @@ class CanonicalResolver {
|
|
|
929
939
|
const closureMap = new Map();
|
|
930
940
|
for (const item of initialSelection) {
|
|
931
941
|
const rawEst = skillsDict[item.id]?.estimatedTokens || 1000;
|
|
932
|
-
const estTokens =
|
|
942
|
+
const estTokens = rawEst;
|
|
933
943
|
closureMap.set(item.id, {
|
|
934
944
|
id: item.id,
|
|
935
945
|
displayName: skillsDict[item.id]?.displayName || item.id,
|
|
@@ -953,7 +963,7 @@ class CanonicalResolver {
|
|
|
953
963
|
}
|
|
954
964
|
} else {
|
|
955
965
|
const rawEst = skillsDict[reqId]?.estimatedTokens || 1000;
|
|
956
|
-
const estTokens =
|
|
966
|
+
const estTokens = rawEst;
|
|
957
967
|
closureMap.set(reqId, {
|
|
958
968
|
id: reqId,
|
|
959
969
|
displayName: skillsDict[reqId]?.displayName || reqId,
|
|
@@ -1000,7 +1010,12 @@ class CanonicalResolver {
|
|
|
1000
1010
|
}
|
|
1001
1011
|
|
|
1002
1012
|
// 7. Token Budget Planner & Capability Clustering
|
|
1003
|
-
const
|
|
1013
|
+
const plannedPhase = resolvePhaseAndRole(explicitPhase, task, []).phase.value;
|
|
1014
|
+
const BASE_SKILLS = ['engineering-workflow'];
|
|
1015
|
+
if (risk.value !== 'routine' && plannedPhase === 'Build') BASE_SKILLS.push('ponytail-mindset');
|
|
1016
|
+
for (let i = BASE_SKILLS.length - 1; i >= 0; i--) {
|
|
1017
|
+
if (!skillsDict[BASE_SKILLS[i]]) BASE_SKILLS.splice(i, 1);
|
|
1018
|
+
}
|
|
1004
1019
|
let baseTokens = 0;
|
|
1005
1020
|
|
|
1006
1021
|
for (const baseId of BASE_SKILLS) {
|
|
@@ -1009,7 +1024,8 @@ class CanonicalResolver {
|
|
|
1009
1024
|
}
|
|
1010
1025
|
}
|
|
1011
1026
|
|
|
1012
|
-
|
|
1027
|
+
// Foundation bodies occupy the same budget as task-specific skills.
|
|
1028
|
+
let allocatedDynamicTokens = baseTokens;
|
|
1013
1029
|
const admitted = new Map(); // id -> candidate entry
|
|
1014
1030
|
|
|
1015
1031
|
// Separate mandatory vs optional candidates
|
|
@@ -1017,7 +1033,7 @@ class CanonicalResolver {
|
|
|
1017
1033
|
const optionalCandidates = [];
|
|
1018
1034
|
|
|
1019
1035
|
for (const cand of closureMap.values()) {
|
|
1020
|
-
const isMandatory = cand.reasons.some(r => r.kind === 'explicit' || r.kind === 'profile_required');
|
|
1036
|
+
const isMandatory = cand.reasons.some(r => r.kind === 'explicit' || r.kind === 'profile_required' || r.kind === 'safety_required');
|
|
1021
1037
|
if (isMandatory) {
|
|
1022
1038
|
mandatoryCandidates.push(cand);
|
|
1023
1039
|
} else {
|
|
@@ -1131,6 +1147,25 @@ class CanonicalResolver {
|
|
|
1131
1147
|
}
|
|
1132
1148
|
}
|
|
1133
1149
|
|
|
1150
|
+
const totalSelectedTokens = finalSelected.reduce((sum, skill) => sum + skill.estimatedTokens, 0);
|
|
1151
|
+
for (const skill of finalSelected) {
|
|
1152
|
+
if (!skillsDict[skill.id]) {
|
|
1153
|
+
warnings.push({ code: 'CTX_SELECTED_SKILL_UNAVAILABLE', message: `Selected skill "${skill.id}" has no installed manifest. Install it before assembling runtime context.` });
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
if (finalSelected.length > maxSkillsLimit) {
|
|
1157
|
+
warnings.push({ code: 'CTX_RESOLVER_SKILL_LIMIT_EXCEEDED', message: `Required skills and dependencies occupy ${finalSelected.length} slots; soft maxSkills limit is ${maxSkillsLimit}.` });
|
|
1158
|
+
}
|
|
1159
|
+
if (finalSelected.length > maxSkillsLimit) {
|
|
1160
|
+
warnings.push({ code: 'CTX_RESOLVER_SKILL_LIMIT_EXCEEDED', message: `Required skills and dependencies occupy ${finalSelected.length} slots; soft maxSkills limit is ${maxSkillsLimit}.` });
|
|
1161
|
+
}
|
|
1162
|
+
if (totalSelectedTokens > budgetTokens && !warnings.some(w => w.code === 'CTX_RESOLVER_BUDGET_EXCEEDED')) {
|
|
1163
|
+
warnings.push({
|
|
1164
|
+
code: 'CTX_RESOLVER_BUDGET_EXCEEDED',
|
|
1165
|
+
message: `Soft context budget (${budgetTokens} tokens) exceeded by required/foundation skill bodies (total: ${totalSelectedTokens} tokens). Client instructions and chat history are additional.`,
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1134
1169
|
// 9. Domain, Phase & Role
|
|
1135
1170
|
let domain = request.domain || '';
|
|
1136
1171
|
if (!domain) {
|
|
@@ -1208,7 +1243,7 @@ class CanonicalResolver {
|
|
|
1208
1243
|
excluded,
|
|
1209
1244
|
conflicts,
|
|
1210
1245
|
warnings,
|
|
1211
|
-
totalEstimatedTokens:
|
|
1246
|
+
totalEstimatedTokens: totalSelectedTokens,
|
|
1212
1247
|
// Legacy compatibility properties
|
|
1213
1248
|
skills: allSelectedIds,
|
|
1214
1249
|
};
|
|
@@ -1229,6 +1264,7 @@ class CanonicalResolver {
|
|
|
1229
1264
|
const lines = [
|
|
1230
1265
|
`[DOMAIN: ${res.domain}] [PHASE: ${phaseStr}] [ROLE: ${res.role}] [MODE: ${modeStr}] [LENSES: ${lensesStr}] [RISK: ${riskStr}]`,
|
|
1231
1266
|
`Skills loaded: ${skillsList}`,
|
|
1267
|
+
'Selection declaration only; skill bodies are read by the consuming client or prompt assembler.',
|
|
1232
1268
|
];
|
|
1233
1269
|
if (res.workflow && res.workflow.steps && res.workflow.steps.length > 0) {
|
|
1234
1270
|
lines.push(`Workflow (${res.workflow.name}): ${res.workflow.steps[0]}`);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function parseResolveArgs(args) {
|
|
4
|
+
const result = { task: '', files: [], explicitPhase: undefined, contextBudgetTokens: undefined, json: false, explain: false };
|
|
5
|
+
const task = [];
|
|
6
|
+
for (let i = 0; i < args.length; i++) {
|
|
7
|
+
const arg = args[i];
|
|
8
|
+
if (arg === '--json') result.json = true;
|
|
9
|
+
else if (arg === '--explain') result.explain = true;
|
|
10
|
+
else if (['--files', '--phase', '--budget'].includes(arg)) {
|
|
11
|
+
const value = args[++i];
|
|
12
|
+
if (!value || value.startsWith('--')) throw new Error(`Missing value for ${arg}`);
|
|
13
|
+
if (arg === '--files') result.files = value.split(',').map(file => file.trim()).filter(Boolean);
|
|
14
|
+
else if (arg === '--phase') result.explicitPhase = value;
|
|
15
|
+
else {
|
|
16
|
+
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value))) {
|
|
17
|
+
throw new Error('--budget must be a positive integer');
|
|
18
|
+
}
|
|
19
|
+
result.contextBudgetTokens = Number(value);
|
|
20
|
+
}
|
|
21
|
+
} else if (arg === '--') {
|
|
22
|
+
task.push(...args.slice(i + 1));
|
|
23
|
+
break;
|
|
24
|
+
} else if (arg.startsWith('-')) throw new Error(`Unknown resolve option: ${arg}`);
|
|
25
|
+
else task.push(arg);
|
|
26
|
+
}
|
|
27
|
+
result.task = task.join(' ');
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { parseResolveArgs };
|
package/.agents/stats.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 1. Full context payload (all skills + AGENTS.md)
|
|
7
7
|
* 2. Profiled context payload (after applying active profile exclusions)
|
|
8
8
|
* 3. Dynamically resolved context (lean on-demand skill set for typical tasks)
|
|
9
|
-
* 4.
|
|
9
|
+
* 4. Character-based payload estimates, not provider billing or quality results
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
'use strict';
|
|
@@ -22,7 +22,6 @@ function estimateTokens(text) {
|
|
|
22
22
|
|
|
23
23
|
function calculateContextStats(projectDir = process.cwd()) {
|
|
24
24
|
const agentsDir = path.join(projectDir, '.agents');
|
|
25
|
-
const skillsDir = path.join(agentsDir, 'core', 'skills');
|
|
26
25
|
const agentsMdPath = path.join(agentsDir, 'AGENTS.md');
|
|
27
26
|
|
|
28
27
|
let agentsMdChars = 0;
|
|
@@ -31,15 +30,13 @@ function calculateContextStats(projectDir = process.cwd()) {
|
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
const skillCharMap = new Map();
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const skillMd = path.join(skillsDir, entry.name, 'SKILL.md');
|
|
33
|
+
const { collectSkillDirectories } = require('./adapters/shared.js');
|
|
34
|
+
const allDirs = collectSkillDirectories({ exclude_skills: [] }, projectDir);
|
|
35
|
+
for (const directory of allDirs) {
|
|
36
|
+
const skillMd = path.join(directory, 'SKILL.md');
|
|
39
37
|
if (fs.existsSync(skillMd)) {
|
|
40
|
-
skillCharMap.set(
|
|
38
|
+
skillCharMap.set(path.basename(directory), fs.readFileSync(skillMd, 'utf8').length);
|
|
41
39
|
}
|
|
42
|
-
}
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
const totalSkillCount = skillCharMap.size;
|
|
@@ -50,9 +47,9 @@ function calculateContextStats(projectDir = process.cwd()) {
|
|
|
50
47
|
|
|
51
48
|
// Active profile
|
|
52
49
|
const activeProfile = profiles.getActiveProfile(projectDir);
|
|
53
|
-
const profileName = activeProfile ? (activeProfile.name || activeProfile.profile) : '
|
|
50
|
+
const profileName = activeProfile ? (activeProfile.name || activeProfile.profile) : 'none';
|
|
54
51
|
const excludedSkills = new Set(
|
|
55
|
-
activeProfile ? activeProfile.exclude_skills || [] :
|
|
52
|
+
activeProfile ? activeProfile.exclude_skills || [] : []
|
|
56
53
|
);
|
|
57
54
|
|
|
58
55
|
let profileChars = agentsMdChars;
|
|
@@ -23,6 +23,8 @@ const DEFAULT_IGNORED_DIRS = new Set([
|
|
|
23
23
|
'.git',
|
|
24
24
|
'node_modules',
|
|
25
25
|
'.contextos-worktrees',
|
|
26
|
+
'.swarm-worktrees',
|
|
27
|
+
'.external-skills',
|
|
26
28
|
'.agents',
|
|
27
29
|
'dist',
|
|
28
30
|
'build',
|
|
@@ -121,6 +123,7 @@ class WorkspaceGraphBuilder {
|
|
|
121
123
|
this.maxDepth = options.maxDepth || 8;
|
|
122
124
|
this.maxPackages = options.maxPackages || 500;
|
|
123
125
|
this.ignoredDirs = new Set([...DEFAULT_IGNORED_DIRS, ...(options.ignoredDirs || [])]);
|
|
126
|
+
this.includeNestedRepositories = options.includeNestedRepositories === true;
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
/**
|
|
@@ -223,7 +226,11 @@ class WorkspaceGraphBuilder {
|
|
|
223
226
|
return;
|
|
224
227
|
}
|
|
225
228
|
|
|
226
|
-
for (const entry of entries) {
|
|
229
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
230
|
+
if (packages.length >= this.maxPackages) {
|
|
231
|
+
hitLimit = true;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
227
234
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
228
235
|
if (this.ignoredDirs.has(entry.name)) continue;
|
|
229
236
|
|
|
@@ -236,10 +243,8 @@ class WorkspaceGraphBuilder {
|
|
|
236
243
|
}
|
|
237
244
|
|
|
238
245
|
// Symlink escape guard: must remain within root realpath
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
!realEntryPath.startsWith(realRoot)
|
|
242
|
-
) {
|
|
246
|
+
const realRelative = path.relative(realRoot, realEntryPath);
|
|
247
|
+
if (realRelative === '..' || realRelative.startsWith(`..${path.sep}`) || path.isAbsolute(realRelative)) {
|
|
243
248
|
continue;
|
|
244
249
|
}
|
|
245
250
|
|
|
@@ -253,7 +258,12 @@ class WorkspaceGraphBuilder {
|
|
|
253
258
|
workspaceGlobs.length === 0 ||
|
|
254
259
|
workspaceGlobs.some((g) => g.test(relPath));
|
|
255
260
|
|
|
256
|
-
|
|
261
|
+
if (fs.existsSync(path.join(fullPath, '.git')) &&
|
|
262
|
+
!this.includeNestedRepositories && !(workspaceGlobs.length > 0 && isDeclared)) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const pkg = isDeclared ? this._inspectDirectoryForPackage(fullPath, relPath, realRoot) : null;
|
|
257
267
|
if (pkg) {
|
|
258
268
|
packages.push(pkg);
|
|
259
269
|
packageIdSet.add(pkg.id);
|