clearctx 3.0.0 → 3.1.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/README.md +1 -0
- package/bin/setup.js +33 -1
- package/package.json +3 -2
- package/skills/api-design/SKILL.md +796 -0
- package/skills/devops/SKILL.md +1043 -0
- package/skills/index.json +53 -0
- package/skills/nodejs-backend/SKILL.md +853 -0
- package/skills/postgresql/SKILL.md +315 -0
- package/skills/react-frontend/SKILL.md +683 -0
- package/skills/security/SKILL.md +1000 -0
- package/skills/testing-qa/SKILL.md +842 -0
- package/skills/typescript/SKILL.md +932 -0
- package/src/mcp-server.js +126 -1
- package/src/prompts.js +47 -2
- package/src/skill-registry.js +182 -0
- package/src/stream-session.js +22 -2
- package/STRATEGY.md +0 -485
package/src/mcp-server.js
CHANGED
|
@@ -48,6 +48,9 @@ const DiffEngine = require('./diff-engine');
|
|
|
48
48
|
const BriefingGenerator = require('./briefing-generator');
|
|
49
49
|
const DecisionJournal = require('./decision-journal');
|
|
50
50
|
const PatternRegistry = require('./pattern-registry');
|
|
51
|
+
|
|
52
|
+
// Skills system
|
|
53
|
+
const skillRegistry = require('./skill-registry');
|
|
51
54
|
const StaleDetector = require('./stale-detector');
|
|
52
55
|
|
|
53
56
|
// Capture version at load time — used to detect stale server processes
|
|
@@ -518,6 +521,7 @@ const TOOLS = [
|
|
|
518
521
|
permission_mode: { type: 'string', enum: ['default', 'acceptEdits', 'bypassPermissions', 'plan'], description: 'Permission mode. Use bypassPermissions to allow sessions to write files without approval (default: bypassPermissions)' },
|
|
519
522
|
team: { type: 'string', description: 'Team name (default: "default")' },
|
|
520
523
|
work_dir: { type: 'string', description: 'Working directory for the session (default: current directory)' },
|
|
524
|
+
skills: { type: 'array', items: { type: 'string' }, description: 'Optional expertise skill IDs to inject into worker (e.g., ["postgresql", "nodejs-backend"]). Use skill_detect to auto-match.' }
|
|
521
525
|
},
|
|
522
526
|
required: ['name', 'prompt'],
|
|
523
527
|
},
|
|
@@ -1265,6 +1269,45 @@ const TOOLS = [
|
|
|
1265
1269
|
},
|
|
1266
1270
|
required: ['projectPath', 'patternId']
|
|
1267
1271
|
}
|
|
1272
|
+
},
|
|
1273
|
+
|
|
1274
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
1275
|
+
// Skills System — expertise domain skills for workers
|
|
1276
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
1277
|
+
|
|
1278
|
+
{
|
|
1279
|
+
name: 'skill_list',
|
|
1280
|
+
description: 'List all available expertise skills with descriptions, domains, and keywords.',
|
|
1281
|
+
inputSchema: {
|
|
1282
|
+
type: 'object',
|
|
1283
|
+
properties: {
|
|
1284
|
+
team: { type: 'string', description: 'Team name (default: "default")' }
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
name: 'skill_get',
|
|
1290
|
+
description: 'Read a specific expertise skill\'s full content including Worker Context, Conventions, Patterns, and Anti-Patterns.',
|
|
1291
|
+
inputSchema: {
|
|
1292
|
+
type: 'object',
|
|
1293
|
+
properties: {
|
|
1294
|
+
skillId: { type: 'string', description: 'Skill ID (e.g., "postgresql", "nodejs-backend", "react-frontend")' },
|
|
1295
|
+
section: { type: 'string', description: 'Optional: extract only a specific section ("worker-context", "conventions", "patterns", "anti-patterns", "integration"). Omit for full content.' }
|
|
1296
|
+
},
|
|
1297
|
+
required: ['skillId']
|
|
1298
|
+
}
|
|
1299
|
+
},
|
|
1300
|
+
{
|
|
1301
|
+
name: 'skill_detect',
|
|
1302
|
+
description: 'Auto-detect which expertise skills are relevant for a given task description. Returns up to 3 matched skills ranked by relevance.',
|
|
1303
|
+
inputSchema: {
|
|
1304
|
+
type: 'object',
|
|
1305
|
+
properties: {
|
|
1306
|
+
task: { type: 'string', description: 'Task description to match skills against' },
|
|
1307
|
+
role: { type: 'string', description: 'Optional worker role for role-based skill matching fallback' }
|
|
1308
|
+
},
|
|
1309
|
+
required: ['task']
|
|
1310
|
+
}
|
|
1268
1311
|
}
|
|
1269
1312
|
];
|
|
1270
1313
|
|
|
@@ -1539,6 +1582,63 @@ async function executeTool(toolName, args) {
|
|
|
1539
1582
|
return textResult(JSON.stringify(result, null, 2));
|
|
1540
1583
|
}
|
|
1541
1584
|
|
|
1585
|
+
// ── Skills System handlers ──
|
|
1586
|
+
case 'skill_list': {
|
|
1587
|
+
const skills = skillRegistry.listSkills();
|
|
1588
|
+
return textResult(JSON.stringify({
|
|
1589
|
+
skills,
|
|
1590
|
+
count: skills.length,
|
|
1591
|
+
message: `${skills.length} expertise skills available. Use skill_get to read full content, or skill_detect to auto-match skills to a task.`
|
|
1592
|
+
}, null, 2));
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
case 'skill_get': {
|
|
1596
|
+
const { skillId, section } = args;
|
|
1597
|
+
const content = skillRegistry.loadSkill(skillId);
|
|
1598
|
+
if (!content) {
|
|
1599
|
+
return errorResult(`Skill "${skillId}" not found. Use skill_list to see available skills.`);
|
|
1600
|
+
}
|
|
1601
|
+
if (section) {
|
|
1602
|
+
// Extract specific section
|
|
1603
|
+
const sectionMap = {
|
|
1604
|
+
'worker-context': '## Worker Context',
|
|
1605
|
+
'conventions': '## Conventions',
|
|
1606
|
+
'patterns': '## Common Patterns',
|
|
1607
|
+
'anti-patterns': '## Anti-Patterns',
|
|
1608
|
+
'integration': '## Integration Notes'
|
|
1609
|
+
};
|
|
1610
|
+
const heading = sectionMap[section];
|
|
1611
|
+
if (!heading) {
|
|
1612
|
+
return errorResult(`Unknown section "${section}". Valid: worker-context, conventions, patterns, anti-patterns, integration`);
|
|
1613
|
+
}
|
|
1614
|
+
const regex = new RegExp(`${heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\n([\\s\\S]*?)(?=\\n## |$)`);
|
|
1615
|
+
const match = content.match(regex);
|
|
1616
|
+
return textResult(JSON.stringify({
|
|
1617
|
+
skillId,
|
|
1618
|
+
section,
|
|
1619
|
+
content: match ? match[1].trim() : 'Section not found in skill.'
|
|
1620
|
+
}, null, 2));
|
|
1621
|
+
}
|
|
1622
|
+
return textResult(JSON.stringify({ skillId, content }, null, 2));
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
case 'skill_detect': {
|
|
1626
|
+
const { task, role } = args;
|
|
1627
|
+
const detected = skillRegistry.detectSkills(task, role);
|
|
1628
|
+
const skills = detected.map(id => {
|
|
1629
|
+
const all = skillRegistry.listSkills();
|
|
1630
|
+
const skill = all.find(s => s.id === id);
|
|
1631
|
+
return skill || { id, description: 'Unknown skill' };
|
|
1632
|
+
});
|
|
1633
|
+
return textResult(JSON.stringify({
|
|
1634
|
+
detected: skills,
|
|
1635
|
+
count: skills.length,
|
|
1636
|
+
message: skills.length > 0
|
|
1637
|
+
? `Detected ${skills.length} relevant skill(s): ${detected.join(', ')}. Use team_spawn with skills parameter to inject into workers.`
|
|
1638
|
+
: 'No skills matched. Provide more detail or assign skills manually.'
|
|
1639
|
+
}, null, 2));
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1542
1642
|
default:
|
|
1543
1643
|
return errorResult(`Unknown tool: ${toolName}`);
|
|
1544
1644
|
}
|
|
@@ -1935,7 +2035,27 @@ async function handleTeamSpawn(args) {
|
|
|
1935
2035
|
const roster = teamHub.getRoster();
|
|
1936
2036
|
|
|
1937
2037
|
// Build team system prompt (now includes role-specific guidance, examples, anti-patterns)
|
|
1938
|
-
|
|
2038
|
+
let teamSystemPrompt = buildTeamSystemPrompt(args.name, args.role, args.task, roster, teamName);
|
|
2039
|
+
|
|
2040
|
+
// Role-based skill fallback (only if skills param not provided at all)
|
|
2041
|
+
// Explicitly passing skills: [] opts out of auto-injection
|
|
2042
|
+
if (args.skills === undefined && args.role) {
|
|
2043
|
+
const roleSkills = skillRegistry.getSkillsForRole(args.role);
|
|
2044
|
+
if (roleSkills.length > 0) {
|
|
2045
|
+
args.skills = roleSkills;
|
|
2046
|
+
// Log that we auto-detected
|
|
2047
|
+
log({ event: 'skill_auto_detect', worker: args.name, role: args.role, detected: roleSkills });
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
// Inject expertise skills if provided
|
|
2052
|
+
if (args.skills && Array.isArray(args.skills) && args.skills.length > 0) {
|
|
2053
|
+
const skillsContext = skillRegistry.getWorkerContext(args.skills);
|
|
2054
|
+
if (skillsContext) {
|
|
2055
|
+
teamSystemPrompt += '\n\n## EXPERTISE SKILLS\n\nThe following domain expertise has been assigned to you. Follow these patterns and conventions:\n\n' + skillsContext;
|
|
2056
|
+
log({ event: 'skills_injected', worker: args.name, skills: args.skills });
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
1939
2059
|
|
|
1940
2060
|
// Spawn the session with team system prompt appended
|
|
1941
2061
|
// Default to bypassPermissions so team sessions can write files without manual approval
|
|
@@ -1969,6 +2089,11 @@ async function handleTeamSpawn(args) {
|
|
|
1969
2089
|
result.turns = response.turns;
|
|
1970
2090
|
}
|
|
1971
2091
|
|
|
2092
|
+
// Add skill info if skills were injected
|
|
2093
|
+
if (args.skills && args.skills.length > 0) {
|
|
2094
|
+
result._skills_injected = args.skills;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
1972
2097
|
// Auto version check on first spawn
|
|
1973
2098
|
const staleness = getCachedStalenessWarning();
|
|
1974
2099
|
if (staleness) {
|
package/src/prompts.js
CHANGED
|
@@ -687,6 +687,10 @@ When all workers are done:
|
|
|
687
687
|
| Quick one-off task | \`delegate_task\` | \`team_spawn\` |
|
|
688
688
|
| Need safety limits (cost/turns) | \`delegate_task\` | \`team_spawn\` |
|
|
689
689
|
| Verify phase completion | \`phase_gate\` |
|
|
690
|
+
| Auto-detect skills for a task | \`skill_detect\` |
|
|
691
|
+
| List available skills | \`skill_list\` |
|
|
692
|
+
| Read a skill's content | \`skill_get\` |
|
|
693
|
+
| Spawn worker with skills | \`team_spawn\` with \`skills\` parameter |
|
|
690
694
|
| Clean up between runs | \`team_reset\` |
|
|
691
695
|
|
|
692
696
|
## WHAT GOES WRONG (And How to Avoid It)
|
|
@@ -786,6 +790,33 @@ When done:
|
|
|
786
790
|
6. Publish your output as an artifact with artifact_publish
|
|
787
791
|
7. Broadcast completion with team_broadcast
|
|
788
792
|
8. Update your status to idle"
|
|
793
|
+
|
|
794
|
+
### Rule 8: Use expertise skills for domain work
|
|
795
|
+
|
|
796
|
+
When spawning workers, assign relevant expertise skills to improve output quality:
|
|
797
|
+
|
|
798
|
+
1. Call \`skill_detect\` with the task description to auto-match skills
|
|
799
|
+
2. Pass matched skills to \`team_spawn\` via the \`skills\` parameter:
|
|
800
|
+
\`\`\`
|
|
801
|
+
team_spawn({
|
|
802
|
+
name: "db-dev",
|
|
803
|
+
role: "database",
|
|
804
|
+
skills: ["postgresql"],
|
|
805
|
+
prompt: "Create the database schema..."
|
|
806
|
+
})
|
|
807
|
+
\`\`\`
|
|
808
|
+
3. Override auto-detection with explicit skills when you know better
|
|
809
|
+
4. Workers with skills produce higher-quality, convention-compliant output
|
|
810
|
+
5. Available skills: postgresql, nodejs-backend, react-frontend, testing-qa, api-design, devops, security, typescript
|
|
811
|
+
|
|
812
|
+
**Role-based fallback:** If you don't specify skills but provide a role, the system auto-detects relevant skills:
|
|
813
|
+
- database → postgresql
|
|
814
|
+
- backend → nodejs-backend, api-design
|
|
815
|
+
- frontend → react-frontend
|
|
816
|
+
- QA/testing → testing-qa
|
|
817
|
+
- devops → devops
|
|
818
|
+
- security → security
|
|
819
|
+
- fullstack → nodejs-backend, react-frontend, typescript
|
|
789
820
|
`;
|
|
790
821
|
}
|
|
791
822
|
|
|
@@ -931,11 +962,25 @@ function getRolePrompt(role) {
|
|
|
931
962
|
* @param {string} opts.teamName — Team name
|
|
932
963
|
* @returns {string} Complete system prompt
|
|
933
964
|
*/
|
|
934
|
-
function buildFullTeamPrompt({ name, role, task, roster, teamName }) {
|
|
965
|
+
function buildFullTeamPrompt({ name, role, task, roster, teamName, skills }) {
|
|
935
966
|
const basePrompt = buildTeamWorkerPrompt(name, role, task, roster, teamName);
|
|
936
967
|
const rolePrompt = getRolePrompt(role);
|
|
937
968
|
|
|
938
|
-
|
|
969
|
+
// Inject expertise skills if provided
|
|
970
|
+
let skillsPrompt = '';
|
|
971
|
+
if (skills && skills.length > 0) {
|
|
972
|
+
try {
|
|
973
|
+
const skillRegistry = require('./skill-registry');
|
|
974
|
+
const context = skillRegistry.getWorkerContext(skills);
|
|
975
|
+
if (context) {
|
|
976
|
+
skillsPrompt = '\n\n## DOMAIN EXPERTISE\n\nYou have been assigned the following expertise skills. Follow these patterns, conventions, and anti-patterns strictly:\n\n' + context + '\n';
|
|
977
|
+
}
|
|
978
|
+
} catch (e) {
|
|
979
|
+
// Skills not available — degrade gracefully
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
return basePrompt + skillsPrompt + rolePrompt;
|
|
939
984
|
}
|
|
940
985
|
|
|
941
986
|
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// Cache for manifest
|
|
5
|
+
let manifestCache = null;
|
|
6
|
+
|
|
7
|
+
// Skills directory path (relative to this module)
|
|
8
|
+
const SKILLS_DIR = path.join(__dirname, '..', 'skills');
|
|
9
|
+
|
|
10
|
+
// Keyword triggers for skill detection
|
|
11
|
+
const SKILL_TRIGGERS = {
|
|
12
|
+
'postgresql': ['postgres', 'postgresql', 'pg', 'database', 'schema', 'migration', 'sql', 'db'],
|
|
13
|
+
'nodejs-backend': ['node', 'express', 'backend', 'server', 'api', 'rest', 'endpoint', 'middleware'],
|
|
14
|
+
'react-frontend': ['react', 'frontend', 'component', 'ui', 'jsx', 'tsx', 'hook', 'state'],
|
|
15
|
+
'testing-qa': ['test', 'testing', 'qa', 'jest', 'mocha', 'integration', 'unit test', 'e2e'],
|
|
16
|
+
'api-design': ['api', 'rest', 'endpoint', 'openapi', 'swagger', 'route', 'pagination'],
|
|
17
|
+
'devops': ['docker', 'ci/cd', 'deploy', 'pipeline', 'kubernetes', 'nginx', 'devops'],
|
|
18
|
+
'security': ['auth', 'security', 'jwt', 'oauth', 'encryption', 'owasp', 'xss', 'csrf'],
|
|
19
|
+
'typescript': ['typescript', 'ts', 'type', 'interface', 'generic', 'typed']
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Role-based skill mapping (keys should be lowercase for case-insensitive lookup)
|
|
23
|
+
const ROLE_SKILLS = {
|
|
24
|
+
'database': ['postgresql'],
|
|
25
|
+
'backend': ['nodejs-backend', 'api-design'],
|
|
26
|
+
'frontend': ['react-frontend'],
|
|
27
|
+
'qa': ['testing-qa'],
|
|
28
|
+
'testing': ['testing-qa'],
|
|
29
|
+
'fullstack': ['nodejs-backend', 'react-frontend', 'typescript'],
|
|
30
|
+
'devops': ['devops'],
|
|
31
|
+
'security': ['security']
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Load the skills manifest (cached)
|
|
36
|
+
* @returns {Object} The manifest object
|
|
37
|
+
*/
|
|
38
|
+
function loadManifest() {
|
|
39
|
+
if (manifestCache) {
|
|
40
|
+
return manifestCache;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const manifestPath = path.join(SKILLS_DIR, 'index.json');
|
|
44
|
+
const manifestData = fs.readFileSync(manifestPath, 'utf8');
|
|
45
|
+
manifestCache = JSON.parse(manifestData);
|
|
46
|
+
return manifestCache;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* List all available skills
|
|
51
|
+
* @returns {Array} Array of skill objects from manifest
|
|
52
|
+
*/
|
|
53
|
+
function listSkills() {
|
|
54
|
+
const manifest = loadManifest();
|
|
55
|
+
return manifest.skills;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Load a specific skill's full content
|
|
60
|
+
* @param {string} skillId - The skill ID (e.g., 'postgresql')
|
|
61
|
+
* @returns {string|null} The full SKILL.md content, or null if not found
|
|
62
|
+
*/
|
|
63
|
+
function loadSkill(skillId) {
|
|
64
|
+
const manifest = loadManifest();
|
|
65
|
+
const skill = manifest.skills.find(s => s.id === skillId);
|
|
66
|
+
|
|
67
|
+
if (!skill) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const skillPath = path.join(SKILLS_DIR, skillId, 'SKILL.md');
|
|
72
|
+
|
|
73
|
+
if (!fs.existsSync(skillPath)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return fs.readFileSync(skillPath, 'utf8');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Detect relevant skills from task description
|
|
82
|
+
* @param {string} taskDescription - The task text to analyze
|
|
83
|
+
* @returns {Array<string>} Up to 3 skill IDs ranked by relevance
|
|
84
|
+
*/
|
|
85
|
+
function detectSkills(taskDescription) {
|
|
86
|
+
const lowerTask = taskDescription.toLowerCase();
|
|
87
|
+
const skillScores = {};
|
|
88
|
+
|
|
89
|
+
// Initialize scores
|
|
90
|
+
Object.keys(SKILL_TRIGGERS).forEach(skillId => {
|
|
91
|
+
skillScores[skillId] = 0;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Check keyword matches
|
|
95
|
+
Object.entries(SKILL_TRIGGERS).forEach(([skillId, keywords]) => {
|
|
96
|
+
keywords.forEach(keyword => {
|
|
97
|
+
if (lowerTask.includes(keyword.toLowerCase())) {
|
|
98
|
+
skillScores[skillId]++;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Check role matches
|
|
104
|
+
Object.entries(ROLE_SKILLS).forEach(([role, skillIds]) => {
|
|
105
|
+
if (lowerTask.includes(role.toLowerCase())) {
|
|
106
|
+
skillIds.forEach(skillId => {
|
|
107
|
+
skillScores[skillId] += 2; // Role matches get higher weight
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Sort by score and return top 3
|
|
113
|
+
const rankedSkills = Object.entries(skillScores)
|
|
114
|
+
.filter(([_, score]) => score > 0)
|
|
115
|
+
.sort((a, b) => b[1] - a[1])
|
|
116
|
+
.slice(0, 3)
|
|
117
|
+
.map(([skillId, _]) => skillId);
|
|
118
|
+
|
|
119
|
+
return rankedSkills;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Extract Worker Context sections from multiple skills
|
|
124
|
+
* @param {Array<string>} skillIds - Array of skill IDs to load
|
|
125
|
+
* @returns {string} Concatenated worker context content with headers
|
|
126
|
+
*/
|
|
127
|
+
function getWorkerContext(skillIds) {
|
|
128
|
+
if (!Array.isArray(skillIds) || skillIds.length === 0) {
|
|
129
|
+
return '';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const manifest = loadManifest();
|
|
133
|
+
const contexts = [];
|
|
134
|
+
|
|
135
|
+
skillIds.forEach(skillId => {
|
|
136
|
+
const skill = manifest.skills.find(s => s.id === skillId);
|
|
137
|
+
if (!skill) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const skillContent = loadSkill(skillId);
|
|
142
|
+
if (!skillContent) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Extract ONLY the "## Worker Context" section (not the full SKILL.md)
|
|
147
|
+
// This keeps injected content small enough for Windows CLI arg limits
|
|
148
|
+
const lines = skillContent.split(/\r?\n/);
|
|
149
|
+
const workerIdx = lines.findIndex(l => l.trim() === '## Worker Context');
|
|
150
|
+
|
|
151
|
+
if (workerIdx !== -1) {
|
|
152
|
+
const nextHeaderIdx = lines.slice(workerIdx + 1).findIndex(l => /^## /.test(l));
|
|
153
|
+
const endIdx = nextHeaderIdx !== -1 ? workerIdx + 1 + nextHeaderIdx : lines.length;
|
|
154
|
+
const contextContent = lines.slice(workerIdx + 1, endIdx).join('\n').trim();
|
|
155
|
+
|
|
156
|
+
if (contextContent) {
|
|
157
|
+
const skillName = skill.description.split(' — ')[0] || skill.description.split(',')[0];
|
|
158
|
+
contexts.push(`### ${skillName}\n${contextContent}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return contexts.join('\n\n');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get skills for a given role using ROLE_SKILLS mapping
|
|
168
|
+
* @param {string} role - The worker role (case-insensitive)
|
|
169
|
+
* @returns {Array<string>} Skill IDs for the role, or empty array if no match
|
|
170
|
+
*/
|
|
171
|
+
function getSkillsForRole(role) {
|
|
172
|
+
if (!role) return [];
|
|
173
|
+
return ROLE_SKILLS[role.toLowerCase()] || [];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = {
|
|
177
|
+
listSkills,
|
|
178
|
+
loadSkill,
|
|
179
|
+
detectSkills,
|
|
180
|
+
getSkillsForRole,
|
|
181
|
+
getWorkerContext
|
|
182
|
+
};
|
package/src/stream-session.js
CHANGED
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
|
|
31
31
|
const { spawn } = require('child_process');
|
|
32
32
|
const { EventEmitter } = require('events');
|
|
33
|
+
const fs = require('fs');
|
|
34
|
+
const path = require('path');
|
|
35
|
+
const os = require('os');
|
|
33
36
|
|
|
34
37
|
// How many milliseconds to wait for a response before timing out
|
|
35
38
|
const DEFAULT_TIMEOUT = 600000; // 10 minutes
|
|
@@ -219,6 +222,7 @@ class StreamSession extends EventEmitter {
|
|
|
219
222
|
this.process.stdin.end();
|
|
220
223
|
}
|
|
221
224
|
}
|
|
225
|
+
this._cleanupSystemPromptFile();
|
|
222
226
|
}
|
|
223
227
|
|
|
224
228
|
/**
|
|
@@ -229,6 +233,7 @@ class StreamSession extends EventEmitter {
|
|
|
229
233
|
this.status = 'stopped';
|
|
230
234
|
this.process.kill('SIGTERM');
|
|
231
235
|
}
|
|
236
|
+
this._cleanupSystemPromptFile();
|
|
232
237
|
}
|
|
233
238
|
|
|
234
239
|
/**
|
|
@@ -272,6 +277,16 @@ class StreamSession extends EventEmitter {
|
|
|
272
277
|
// Private methods
|
|
273
278
|
// ===========================================================================
|
|
274
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Clean up the temp system prompt file if one was created.
|
|
282
|
+
*/
|
|
283
|
+
_cleanupSystemPromptFile() {
|
|
284
|
+
if (this._systemPromptFile) {
|
|
285
|
+
try { fs.unlinkSync(this._systemPromptFile); } catch (_) {}
|
|
286
|
+
this._systemPromptFile = null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
275
290
|
/**
|
|
276
291
|
* Build claude CLI arguments for stream-json mode.
|
|
277
292
|
*/
|
|
@@ -299,9 +314,14 @@ class StreamSession extends EventEmitter {
|
|
|
299
314
|
args.push('--allowedTools', ...this.allowedTools);
|
|
300
315
|
}
|
|
301
316
|
|
|
302
|
-
// System prompt
|
|
317
|
+
// System prompt — write to temp file to avoid Windows ENAMETOOLONG on long prompts
|
|
303
318
|
if (this.systemPrompt) {
|
|
304
|
-
|
|
319
|
+
const tmpDir = path.join(os.tmpdir(), 'clearctx');
|
|
320
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
321
|
+
const tmpFile = path.join(tmpDir, `sysprompt-${this.name}-${Date.now()}.md`);
|
|
322
|
+
fs.writeFileSync(tmpFile, this.systemPrompt, 'utf8');
|
|
323
|
+
this._systemPromptFile = tmpFile;
|
|
324
|
+
args.push('--append-system-prompt-file', tmpFile);
|
|
305
325
|
}
|
|
306
326
|
|
|
307
327
|
// Budget limit
|