prjct-cli 0.60.1 → 0.60.2
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/CHANGELOG.md +35 -0
- package/core/agentic/orchestrator-executor.ts +31 -33
- package/core/domain/agent-loader.ts +8 -8
- package/dist/bin/prjct.mjs +26 -28
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.60.2] - 2026-02-05
|
|
4
|
+
|
|
5
|
+
### Performance
|
|
6
|
+
|
|
7
|
+
- parallelize agent/skill loading with Promise.all (PRJ-110) (#101)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [0.60.2] - 2026-02-05
|
|
11
|
+
|
|
12
|
+
### Performance
|
|
13
|
+
|
|
14
|
+
- **Parallel agent/skill loading (PRJ-110)**: Agent and skill loading now uses `Promise.all` for parallel I/O
|
|
15
|
+
|
|
16
|
+
### Implementation Details
|
|
17
|
+
|
|
18
|
+
Refactored `loadAgents()` and `loadSkills()` in `core/agentic/orchestrator-executor.ts` to use `Promise.all` with map instead of sequential for loops. Also parallelized `loadAllAgents()` in `core/domain/agent-loader.ts`. Pattern: collect items → map to async promises → Promise.all → filter nulls with type guard.
|
|
19
|
+
|
|
20
|
+
### Learnings
|
|
21
|
+
|
|
22
|
+
- Use `Promise.all(items.map(async (item) => ...))` for parallel async operations
|
|
23
|
+
- Return null for failed items, then filter - can't push to array in parallel
|
|
24
|
+
- Collect unique items first (deduplication), then parallelize reads
|
|
25
|
+
|
|
26
|
+
### Test Plan
|
|
27
|
+
|
|
28
|
+
#### For QA
|
|
29
|
+
1. Run `prjct sync --yes` - verify agents load successfully
|
|
30
|
+
2. Run `p. task "test"` - verify orchestrator works
|
|
31
|
+
3. Check no errors in agent/skill loading output
|
|
32
|
+
|
|
33
|
+
#### For Users
|
|
34
|
+
- Agent and skill loading is now faster (parallel I/O)
|
|
35
|
+
- No changes needed - improvement is automatic
|
|
36
|
+
|
|
37
|
+
|
|
3
38
|
## [0.60.1] - 2026-02-05
|
|
4
39
|
|
|
5
40
|
### Bug Fixes
|
|
@@ -341,13 +341,15 @@ export class OrchestratorExecutor {
|
|
|
341
341
|
*
|
|
342
342
|
* Reads agent markdown files from {globalPath}/agents/
|
|
343
343
|
* and extracts their content and skills from frontmatter.
|
|
344
|
+
*
|
|
345
|
+
* Uses parallel file reads for performance (PRJ-110).
|
|
344
346
|
*/
|
|
345
347
|
async loadAgents(domains: string[], projectId: string): Promise<LoadedAgent[]> {
|
|
346
348
|
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
347
349
|
const agentsDir = path.join(globalPath, 'agents')
|
|
348
|
-
const agents: LoadedAgent[] = []
|
|
349
350
|
|
|
350
|
-
|
|
351
|
+
// Load all domain agents in parallel
|
|
352
|
+
const agentPromises = domains.map(async (domain): Promise<LoadedAgent | null> => {
|
|
351
353
|
// Try exact match first, then variations
|
|
352
354
|
const possibleNames = [`${domain}.md`, `${domain}-agent.md`, `prjct-${domain}.md`]
|
|
353
355
|
|
|
@@ -357,21 +359,22 @@ export class OrchestratorExecutor {
|
|
|
357
359
|
const content = await fs.readFile(filePath, 'utf-8')
|
|
358
360
|
const { frontmatter, body } = this.parseAgentFile(content)
|
|
359
361
|
|
|
360
|
-
|
|
362
|
+
return {
|
|
361
363
|
name: fileName.replace('.md', ''),
|
|
362
364
|
domain,
|
|
363
365
|
content: body,
|
|
364
366
|
skills: frontmatter.skills || [],
|
|
365
367
|
filePath,
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
} catch {}
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
// Try next variation
|
|
371
|
+
}
|
|
371
372
|
}
|
|
372
|
-
|
|
373
|
+
return null
|
|
374
|
+
})
|
|
373
375
|
|
|
374
|
-
|
|
376
|
+
const results = await Promise.all(agentPromises)
|
|
377
|
+
return results.filter((agent): agent is LoadedAgent => agent !== null)
|
|
375
378
|
}
|
|
376
379
|
|
|
377
380
|
/**
|
|
@@ -400,51 +403,46 @@ export class OrchestratorExecutor {
|
|
|
400
403
|
* Load skills from agent frontmatter
|
|
401
404
|
*
|
|
402
405
|
* Skills are stored in ~/.claude/skills/{name}.md
|
|
406
|
+
*
|
|
407
|
+
* Uses parallel file reads for performance (PRJ-110).
|
|
403
408
|
*/
|
|
404
409
|
async loadSkills(agents: LoadedAgent[]): Promise<LoadedSkill[]> {
|
|
405
410
|
const skillsDir = path.join(os.homedir(), '.claude', 'skills')
|
|
406
|
-
const skills: LoadedSkill[] = []
|
|
407
|
-
const loadedSkillNames = new Set<string>()
|
|
408
411
|
|
|
412
|
+
// Collect unique skill names from all agents
|
|
413
|
+
const uniqueSkillNames = new Set<string>()
|
|
409
414
|
for (const agent of agents) {
|
|
410
415
|
for (const skillName of agent.skills) {
|
|
411
|
-
|
|
412
|
-
|
|
416
|
+
uniqueSkillNames.add(skillName)
|
|
417
|
+
}
|
|
418
|
+
}
|
|
413
419
|
|
|
420
|
+
// Load all skills in parallel
|
|
421
|
+
const skillPromises = Array.from(uniqueSkillNames).map(
|
|
422
|
+
async (skillName): Promise<LoadedSkill | null> => {
|
|
414
423
|
// Check both patterns: flat file and subdirectory (ecosystem standard)
|
|
415
424
|
const flatPath = path.join(skillsDir, `${skillName}.md`)
|
|
416
425
|
const subdirPath = path.join(skillsDir, skillName, 'SKILL.md')
|
|
417
426
|
|
|
418
|
-
let content: string | null = null
|
|
419
|
-
let resolvedPath = flatPath
|
|
420
|
-
|
|
421
427
|
// Prefer subdirectory format (ecosystem standard)
|
|
422
428
|
try {
|
|
423
|
-
content = await fs.readFile(subdirPath, 'utf-8')
|
|
424
|
-
|
|
429
|
+
const content = await fs.readFile(subdirPath, 'utf-8')
|
|
430
|
+
return { name: skillName, content, filePath: subdirPath }
|
|
425
431
|
} catch {
|
|
426
432
|
// Fall back to flat file
|
|
427
433
|
try {
|
|
428
|
-
content = await fs.readFile(flatPath, 'utf-8')
|
|
429
|
-
|
|
434
|
+
const content = await fs.readFile(flatPath, 'utf-8')
|
|
435
|
+
return { name: skillName, content, filePath: flatPath }
|
|
430
436
|
} catch {
|
|
431
437
|
// Skill not found - not an error, just skip
|
|
432
|
-
|
|
438
|
+
return null
|
|
433
439
|
}
|
|
434
440
|
}
|
|
435
|
-
|
|
436
|
-
if (content) {
|
|
437
|
-
skills.push({
|
|
438
|
-
name: skillName,
|
|
439
|
-
content,
|
|
440
|
-
filePath: resolvedPath,
|
|
441
|
-
})
|
|
442
|
-
loadedSkillNames.add(skillName)
|
|
443
|
-
}
|
|
444
441
|
}
|
|
445
|
-
|
|
442
|
+
)
|
|
446
443
|
|
|
447
|
-
|
|
444
|
+
const results = await Promise.all(skillPromises)
|
|
445
|
+
return results.filter((skill): skill is LoadedSkill => skill !== null)
|
|
448
446
|
}
|
|
449
447
|
|
|
450
448
|
/**
|
|
@@ -94,22 +94,22 @@ class AgentLoader {
|
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
96
|
* Load all agents for the project
|
|
97
|
+
*
|
|
98
|
+
* Uses parallel file reads for performance (PRJ-110).
|
|
97
99
|
*/
|
|
98
100
|
async loadAllAgents(): Promise<Agent[]> {
|
|
99
101
|
try {
|
|
100
102
|
const files = await fs.readdir(this.agentsDir)
|
|
101
103
|
const agentFiles = files.filter((f) => f.endsWith('.md') && !f.startsWith('.'))
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
// Load all agents in parallel
|
|
106
|
+
const agentPromises = agentFiles.map((file) => {
|
|
105
107
|
const agentName = file.replace('.md', '')
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
agents.push(agent)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
108
|
+
return this.loadAgent(agentName)
|
|
109
|
+
})
|
|
111
110
|
|
|
112
|
-
|
|
111
|
+
const results = await Promise.all(agentPromises)
|
|
112
|
+
return results.filter((agent): agent is Agent => agent !== null)
|
|
113
113
|
} catch (error) {
|
|
114
114
|
if (isNotFoundError(error)) {
|
|
115
115
|
return [] // Agents directory doesn't exist yet
|
package/dist/bin/prjct.mjs
CHANGED
|
@@ -11788,31 +11788,33 @@ var init_orchestrator_executor = __esm({
|
|
|
11788
11788
|
*
|
|
11789
11789
|
* Reads agent markdown files from {globalPath}/agents/
|
|
11790
11790
|
* and extracts their content and skills from frontmatter.
|
|
11791
|
+
*
|
|
11792
|
+
* Uses parallel file reads for performance (PRJ-110).
|
|
11791
11793
|
*/
|
|
11792
11794
|
async loadAgents(domains, projectId) {
|
|
11793
11795
|
const globalPath = path_manager_default.getGlobalProjectPath(projectId);
|
|
11794
11796
|
const agentsDir = path23.join(globalPath, "agents");
|
|
11795
|
-
const
|
|
11796
|
-
for (const domain of domains) {
|
|
11797
|
+
const agentPromises = domains.map(async (domain) => {
|
|
11797
11798
|
const possibleNames = [`${domain}.md`, `${domain}-agent.md`, `prjct-${domain}.md`];
|
|
11798
11799
|
for (const fileName of possibleNames) {
|
|
11799
11800
|
const filePath = path23.join(agentsDir, fileName);
|
|
11800
11801
|
try {
|
|
11801
11802
|
const content = await fs24.readFile(filePath, "utf-8");
|
|
11802
11803
|
const { frontmatter, body } = this.parseAgentFile(content);
|
|
11803
|
-
|
|
11804
|
+
return {
|
|
11804
11805
|
name: fileName.replace(".md", ""),
|
|
11805
11806
|
domain,
|
|
11806
11807
|
content: body,
|
|
11807
11808
|
skills: frontmatter.skills || [],
|
|
11808
11809
|
filePath
|
|
11809
|
-
}
|
|
11810
|
-
break;
|
|
11810
|
+
};
|
|
11811
11811
|
} catch {
|
|
11812
11812
|
}
|
|
11813
11813
|
}
|
|
11814
|
-
|
|
11815
|
-
|
|
11814
|
+
return null;
|
|
11815
|
+
});
|
|
11816
|
+
const results = await Promise.all(agentPromises);
|
|
11817
|
+
return results.filter((agent) => agent !== null);
|
|
11816
11818
|
}
|
|
11817
11819
|
/**
|
|
11818
11820
|
* Parse agent markdown file to extract frontmatter and body
|
|
@@ -11832,40 +11834,36 @@ var init_orchestrator_executor = __esm({
|
|
|
11832
11834
|
* Load skills from agent frontmatter
|
|
11833
11835
|
*
|
|
11834
11836
|
* Skills are stored in ~/.claude/skills/{name}.md
|
|
11837
|
+
*
|
|
11838
|
+
* Uses parallel file reads for performance (PRJ-110).
|
|
11835
11839
|
*/
|
|
11836
11840
|
async loadSkills(agents) {
|
|
11837
11841
|
const skillsDir = path23.join(os8.homedir(), ".claude", "skills");
|
|
11838
|
-
const
|
|
11839
|
-
const loadedSkillNames = /* @__PURE__ */ new Set();
|
|
11842
|
+
const uniqueSkillNames = /* @__PURE__ */ new Set();
|
|
11840
11843
|
for (const agent of agents) {
|
|
11841
11844
|
for (const skillName of agent.skills) {
|
|
11842
|
-
|
|
11845
|
+
uniqueSkillNames.add(skillName);
|
|
11846
|
+
}
|
|
11847
|
+
}
|
|
11848
|
+
const skillPromises = Array.from(uniqueSkillNames).map(
|
|
11849
|
+
async (skillName) => {
|
|
11843
11850
|
const flatPath = path23.join(skillsDir, `${skillName}.md`);
|
|
11844
11851
|
const subdirPath = path23.join(skillsDir, skillName, "SKILL.md");
|
|
11845
|
-
let content = null;
|
|
11846
|
-
let resolvedPath = flatPath;
|
|
11847
11852
|
try {
|
|
11848
|
-
content = await fs24.readFile(subdirPath, "utf-8");
|
|
11849
|
-
|
|
11853
|
+
const content = await fs24.readFile(subdirPath, "utf-8");
|
|
11854
|
+
return { name: skillName, content, filePath: subdirPath };
|
|
11850
11855
|
} catch {
|
|
11851
11856
|
try {
|
|
11852
|
-
content = await fs24.readFile(flatPath, "utf-8");
|
|
11853
|
-
|
|
11857
|
+
const content = await fs24.readFile(flatPath, "utf-8");
|
|
11858
|
+
return { name: skillName, content, filePath: flatPath };
|
|
11854
11859
|
} catch {
|
|
11855
|
-
|
|
11860
|
+
return null;
|
|
11856
11861
|
}
|
|
11857
11862
|
}
|
|
11858
|
-
if (content) {
|
|
11859
|
-
skills.push({
|
|
11860
|
-
name: skillName,
|
|
11861
|
-
content,
|
|
11862
|
-
filePath: resolvedPath
|
|
11863
|
-
});
|
|
11864
|
-
loadedSkillNames.add(skillName);
|
|
11865
|
-
}
|
|
11866
11863
|
}
|
|
11867
|
-
|
|
11868
|
-
|
|
11864
|
+
);
|
|
11865
|
+
const results = await Promise.all(skillPromises);
|
|
11866
|
+
return results.filter((skill) => skill !== null);
|
|
11869
11867
|
}
|
|
11870
11868
|
/**
|
|
11871
11869
|
* Determine if task should be fragmented into subtasks
|
|
@@ -26412,7 +26410,7 @@ var require_package = __commonJS({
|
|
|
26412
26410
|
"package.json"(exports, module) {
|
|
26413
26411
|
module.exports = {
|
|
26414
26412
|
name: "prjct-cli",
|
|
26415
|
-
version: "0.60.
|
|
26413
|
+
version: "0.60.2",
|
|
26416
26414
|
description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
|
|
26417
26415
|
main: "core/index.ts",
|
|
26418
26416
|
bin: {
|