universal-dev-standards 4.1.0 → 4.2.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/bin/uds.js +75 -0
- package/bundled/core/ai-friendly-architecture.md +542 -0
- package/bundled/locales/zh-CN/README.md +210 -509
- package/bundled/locales/zh-CN/core/ai-friendly-architecture.md +306 -0
- package/bundled/locales/zh-CN/docs/AI-AGENT-ROADMAP.md +82 -22
- package/bundled/locales/zh-CN/integrations/gemini-cli/GEMINI.md +35 -3
- package/bundled/locales/zh-CN/integrations/github-copilot/COPILOT-CHAT-REFERENCE.md +89 -3
- package/bundled/locales/zh-CN/integrations/github-copilot/skills-mapping.md +8 -4
- package/bundled/locales/zh-TW/README.md +211 -490
- package/bundled/locales/zh-TW/core/ai-friendly-architecture.md +306 -0
- package/bundled/locales/zh-TW/docs/AI-AGENT-ROADMAP.md +82 -22
- package/bundled/locales/zh-TW/integrations/gemini-cli/GEMINI.md +35 -3
- package/bundled/locales/zh-TW/integrations/github-copilot/COPILOT-CHAT-REFERENCE.md +89 -3
- package/bundled/locales/zh-TW/integrations/github-copilot/skills-mapping.md +8 -4
- package/bundled/skills/claude-code/README.md +8 -0
- package/bundled/skills/claude-code/agents/README.md +305 -0
- package/bundled/skills/claude-code/agents/code-architect.md +259 -0
- package/bundled/skills/claude-code/agents/doc-writer.md +406 -0
- package/bundled/skills/claude-code/agents/reviewer.md +353 -0
- package/bundled/skills/claude-code/agents/spec-analyst.md +374 -0
- package/bundled/skills/claude-code/agents/test-specialist.md +364 -0
- package/bundled/skills/claude-code/workflows/README.md +303 -0
- package/bundled/skills/claude-code/workflows/code-review.workflow.yaml +186 -0
- package/bundled/skills/claude-code/workflows/feature-dev.workflow.yaml +174 -0
- package/bundled/skills/claude-code/workflows/integrated-flow.workflow.yaml +238 -0
- package/bundled/skills/claude-code/workflows/large-codebase-analysis.workflow.yaml +226 -0
- package/package.json +11 -1
- package/src/commands/agent.js +417 -0
- package/src/commands/ai-context.js +552 -0
- package/src/commands/check.js +3 -3
- package/src/commands/init.js +6 -3
- package/src/commands/workflow.js +425 -0
- package/src/config/ai-agent-paths.js +217 -13
- package/src/core/constants.js +514 -0
- package/src/core/errors.js +398 -0
- package/src/core/manifest.js +473 -0
- package/src/core/paths.js +398 -0
- package/src/prompts/init.js +7 -5
- package/src/utils/agent-adapter.js +320 -0
- package/src/utils/agents-installer.js +393 -0
- package/src/utils/context-chunker.js +467 -0
- package/src/utils/copier.js +59 -99
- package/src/utils/hasher.js +2 -16
- package/src/utils/integration-generator.js +28 -52
- package/src/utils/workflows-installer.js +545 -0
- package/standards-registry.json +166 -20
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Adapter
|
|
3
|
+
*
|
|
4
|
+
* Provides cross-tool adaptation for AGENT.md definitions.
|
|
5
|
+
* Adapts agents for different execution modes based on AI tool capabilities.
|
|
6
|
+
*
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { supportsTask, supportsAgents, getAgentConfig } from '../config/ai-agent-paths.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Execution modes for agents
|
|
14
|
+
*/
|
|
15
|
+
export const ExecutionMode = {
|
|
16
|
+
TASK: 'task', // Use Task tool to spawn independent subagent (Claude Code, OpenCode)
|
|
17
|
+
INLINE: 'inline', // Inject agent content as context prefix (Cursor, Windsurf, etc.)
|
|
18
|
+
MANUAL: 'manual' // Generate manual instructions (unsupported tools)
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Determine the execution mode for an agent on a specific AI tool
|
|
23
|
+
* @param {string} aiTool - AI tool identifier
|
|
24
|
+
* @returns {string} Execution mode
|
|
25
|
+
*/
|
|
26
|
+
export function getExecutionMode(aiTool) {
|
|
27
|
+
if (supportsTask(aiTool)) {
|
|
28
|
+
return ExecutionMode.TASK;
|
|
29
|
+
}
|
|
30
|
+
if (supportsAgents(aiTool)) {
|
|
31
|
+
return ExecutionMode.INLINE;
|
|
32
|
+
}
|
|
33
|
+
return ExecutionMode.MANUAL;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Adapt agent configuration for a specific AI tool
|
|
38
|
+
* @param {Object} agentConfig - Parsed agent configuration
|
|
39
|
+
* @param {string} aiTool - Target AI tool identifier
|
|
40
|
+
* @returns {Object} Adapted configuration
|
|
41
|
+
*/
|
|
42
|
+
export function adaptAgentForTool(agentConfig, aiTool) {
|
|
43
|
+
const mode = getExecutionMode(aiTool);
|
|
44
|
+
|
|
45
|
+
switch (mode) {
|
|
46
|
+
case ExecutionMode.TASK:
|
|
47
|
+
return adaptForTaskMode(agentConfig, aiTool);
|
|
48
|
+
case ExecutionMode.INLINE:
|
|
49
|
+
return adaptForInlineMode(agentConfig, aiTool);
|
|
50
|
+
case ExecutionMode.MANUAL:
|
|
51
|
+
return adaptForManualMode(agentConfig, aiTool);
|
|
52
|
+
default:
|
|
53
|
+
return { mode: ExecutionMode.MANUAL, error: `Unknown mode for ${aiTool}` };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Adapt agent for Task tool execution mode
|
|
59
|
+
* @param {Object} agentConfig - Agent configuration
|
|
60
|
+
* @param {string} aiTool - AI tool identifier
|
|
61
|
+
* @returns {Object} Task mode configuration
|
|
62
|
+
*/
|
|
63
|
+
function adaptForTaskMode(agentConfig, aiTool) {
|
|
64
|
+
const taskConfig = {
|
|
65
|
+
subagent_type: agentConfig.name || 'general-purpose',
|
|
66
|
+
description: truncateDescription(agentConfig.description),
|
|
67
|
+
model: agentConfig.model || 'sonnet',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Map allowed/disallowed tools to Task tool format
|
|
71
|
+
if (agentConfig['allowed-tools']) {
|
|
72
|
+
taskConfig.allowedTools = parseToolList(agentConfig['allowed-tools']);
|
|
73
|
+
}
|
|
74
|
+
if (agentConfig['disallowed-tools']) {
|
|
75
|
+
taskConfig.disallowedTools = parseToolList(agentConfig['disallowed-tools']);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Add skill dependencies as context
|
|
79
|
+
if (agentConfig.skills && agentConfig.skills.length > 0) {
|
|
80
|
+
taskConfig.skillDependencies = agentConfig.skills;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
mode: ExecutionMode.TASK,
|
|
85
|
+
aiTool,
|
|
86
|
+
taskConfig,
|
|
87
|
+
triggers: agentConfig.triggers || {}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Adapt agent for inline context injection mode
|
|
93
|
+
* @param {Object} agentConfig - Agent configuration
|
|
94
|
+
* @param {string} aiTool - AI tool identifier
|
|
95
|
+
* @returns {Object} Inline mode configuration
|
|
96
|
+
*/
|
|
97
|
+
function adaptForInlineMode(agentConfig, aiTool) {
|
|
98
|
+
// Generate context prefix for tools that don't support Task tool
|
|
99
|
+
const contextPrefix = generateInlineContextPrefix(agentConfig);
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
mode: ExecutionMode.INLINE,
|
|
103
|
+
aiTool,
|
|
104
|
+
contextPrefix,
|
|
105
|
+
triggers: agentConfig.triggers || {},
|
|
106
|
+
note: 'This tool does not support subagent execution. Agent will be injected as context.'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Adapt agent for manual instruction mode
|
|
112
|
+
* @param {Object} agentConfig - Agent configuration
|
|
113
|
+
* @param {string} aiTool - AI tool identifier
|
|
114
|
+
* @returns {Object} Manual mode configuration
|
|
115
|
+
*/
|
|
116
|
+
function adaptForManualMode(agentConfig, aiTool) {
|
|
117
|
+
const instructions = generateManualInstructions(agentConfig);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
mode: ExecutionMode.MANUAL,
|
|
121
|
+
aiTool,
|
|
122
|
+
instructions,
|
|
123
|
+
note: 'This tool does not support agents. Use these instructions manually.'
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Generate inline context prefix for injection mode
|
|
129
|
+
* @param {Object} agentConfig - Agent configuration
|
|
130
|
+
* @returns {string} Context prefix
|
|
131
|
+
*/
|
|
132
|
+
function generateInlineContextPrefix(agentConfig) {
|
|
133
|
+
const lines = [
|
|
134
|
+
`<!-- UDS Agent: ${agentConfig.name} -->`,
|
|
135
|
+
`<!-- Role: ${agentConfig.role || 'specialist'} -->`,
|
|
136
|
+
''
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
if (agentConfig.expertise && agentConfig.expertise.length > 0) {
|
|
140
|
+
lines.push(`## Agent Expertise: ${agentConfig.expertise.join(', ')}`);
|
|
141
|
+
lines.push('');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (agentConfig.description) {
|
|
145
|
+
lines.push('## Agent Purpose');
|
|
146
|
+
lines.push(cleanDescription(agentConfig.description));
|
|
147
|
+
lines.push('');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Add tool permission hints
|
|
151
|
+
if (agentConfig['allowed-tools']) {
|
|
152
|
+
const tools = parseToolList(agentConfig['allowed-tools']);
|
|
153
|
+
lines.push(`## Allowed Operations: ${tools.join(', ')}`);
|
|
154
|
+
}
|
|
155
|
+
if (agentConfig['disallowed-tools']) {
|
|
156
|
+
const tools = parseToolList(agentConfig['disallowed-tools']);
|
|
157
|
+
lines.push(`## Restricted Operations: ${tools.join(', ')}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
lines.push('');
|
|
161
|
+
lines.push('---');
|
|
162
|
+
lines.push('');
|
|
163
|
+
|
|
164
|
+
return lines.join('\n');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Generate manual instructions for unsupported tools
|
|
169
|
+
* @param {Object} agentConfig - Agent configuration
|
|
170
|
+
* @returns {string} Manual instructions
|
|
171
|
+
*/
|
|
172
|
+
function generateManualInstructions(agentConfig) {
|
|
173
|
+
const lines = [
|
|
174
|
+
`# ${agentConfig.name} Agent Instructions`,
|
|
175
|
+
'',
|
|
176
|
+
'## How to Use This Agent Manually',
|
|
177
|
+
'',
|
|
178
|
+
'1. Copy the agent content below into your AI assistant\'s context',
|
|
179
|
+
'2. Provide your task after the agent instructions',
|
|
180
|
+
'3. The AI will act according to the agent\'s role and expertise',
|
|
181
|
+
'',
|
|
182
|
+
'## Agent Information',
|
|
183
|
+
'',
|
|
184
|
+
`- **Name**: ${agentConfig.name}`,
|
|
185
|
+
`- **Role**: ${agentConfig.role || 'specialist'}`,
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
if (agentConfig.expertise) {
|
|
189
|
+
lines.push(`- **Expertise**: ${agentConfig.expertise.join(', ')}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (agentConfig.description) {
|
|
193
|
+
lines.push('');
|
|
194
|
+
lines.push('## Purpose');
|
|
195
|
+
lines.push(cleanDescription(agentConfig.description));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (agentConfig.triggers && agentConfig.triggers.keywords) {
|
|
199
|
+
lines.push('');
|
|
200
|
+
lines.push('## Trigger Keywords');
|
|
201
|
+
lines.push(`Use this agent when: ${agentConfig.triggers.keywords.join(', ')}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return lines.join('\n');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Parse tool list from various formats
|
|
209
|
+
* @param {string|string[]} tools - Tool list
|
|
210
|
+
* @returns {string[]} Normalized tool list
|
|
211
|
+
*/
|
|
212
|
+
function parseToolList(tools) {
|
|
213
|
+
if (Array.isArray(tools)) {
|
|
214
|
+
return tools;
|
|
215
|
+
}
|
|
216
|
+
if (typeof tools === 'string') {
|
|
217
|
+
// Handle comma-separated or bracket-enclosed format
|
|
218
|
+
return tools
|
|
219
|
+
.replace(/^\[|\]$/g, '')
|
|
220
|
+
.split(',')
|
|
221
|
+
.map(t => t.trim())
|
|
222
|
+
.filter(t => t.length > 0);
|
|
223
|
+
}
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Truncate description for Task tool (short description)
|
|
229
|
+
* @param {string} description - Full description
|
|
230
|
+
* @returns {string} Truncated description (max 50 chars)
|
|
231
|
+
*/
|
|
232
|
+
function truncateDescription(description) {
|
|
233
|
+
if (!description) return 'UDS Agent';
|
|
234
|
+
const cleaned = cleanDescription(description);
|
|
235
|
+
const firstLine = cleaned.split('\n')[0].trim();
|
|
236
|
+
if (firstLine.length <= 50) return firstLine;
|
|
237
|
+
return firstLine.substring(0, 47) + '...';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Clean description text
|
|
242
|
+
* @param {string} description - Description text
|
|
243
|
+
* @returns {string} Cleaned description
|
|
244
|
+
*/
|
|
245
|
+
function cleanDescription(description) {
|
|
246
|
+
if (!description) return '';
|
|
247
|
+
return description
|
|
248
|
+
.replace(/^\|?\s*/, '') // Remove leading pipe and spaces (YAML multiline)
|
|
249
|
+
.replace(/\s+/g, ' ') // Normalize whitespace
|
|
250
|
+
.trim();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get all supported AI tools and their execution modes
|
|
255
|
+
* @returns {Object[]} Array of tool info objects
|
|
256
|
+
*/
|
|
257
|
+
export function getAllToolsWithModes() {
|
|
258
|
+
const tools = [
|
|
259
|
+
'claude-code', 'opencode', 'cursor', 'cline',
|
|
260
|
+
'roo-code', 'codex', 'copilot', 'windsurf', 'gemini-cli'
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
return tools.map(tool => {
|
|
264
|
+
const config = getAgentConfig(tool);
|
|
265
|
+
return {
|
|
266
|
+
id: tool,
|
|
267
|
+
name: config?.name || tool,
|
|
268
|
+
mode: getExecutionMode(tool),
|
|
269
|
+
supportsTask: supportsTask(tool),
|
|
270
|
+
supportsAgents: supportsAgents(tool)
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Check if an agent is compatible with a specific AI tool
|
|
277
|
+
* @param {Object} agentConfig - Agent configuration
|
|
278
|
+
* @param {string} aiTool - AI tool identifier
|
|
279
|
+
* @returns {Object} Compatibility info
|
|
280
|
+
*/
|
|
281
|
+
export function checkAgentToolCompatibility(agentConfig, aiTool) {
|
|
282
|
+
const mode = getExecutionMode(aiTool);
|
|
283
|
+
|
|
284
|
+
const result = {
|
|
285
|
+
compatible: true,
|
|
286
|
+
mode,
|
|
287
|
+
warnings: [],
|
|
288
|
+
features: {
|
|
289
|
+
taskExecution: mode === ExecutionMode.TASK,
|
|
290
|
+
toolPermissions: mode === ExecutionMode.TASK,
|
|
291
|
+
skillDependencies: mode === ExecutionMode.TASK,
|
|
292
|
+
triggers: true
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// Check tool permissions compatibility
|
|
297
|
+
if (agentConfig['allowed-tools'] && mode !== ExecutionMode.TASK) {
|
|
298
|
+
result.warnings.push('Tool permissions will be ignored (inline/manual mode)');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Check model preference compatibility
|
|
302
|
+
if (agentConfig.model && mode !== ExecutionMode.TASK) {
|
|
303
|
+
result.warnings.push('Model preference will be ignored (inline/manual mode)');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Check skill dependencies
|
|
307
|
+
if (agentConfig.skills && agentConfig.skills.length > 0 && mode !== ExecutionMode.TASK) {
|
|
308
|
+
result.warnings.push('Skill dependencies require manual loading (inline/manual mode)');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export default {
|
|
315
|
+
ExecutionMode,
|
|
316
|
+
getExecutionMode,
|
|
317
|
+
adaptAgentForTool,
|
|
318
|
+
getAllToolsWithModes,
|
|
319
|
+
checkAgentToolCompatibility
|
|
320
|
+
};
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agents Installer
|
|
3
|
+
*
|
|
4
|
+
* Provides installation and management of AGENT.md definitions
|
|
5
|
+
* across all supported AI coding assistants.
|
|
6
|
+
*
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
11
|
+
import { dirname, join, basename } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import {
|
|
14
|
+
getAgentConfig,
|
|
15
|
+
getAgentsDirForAgent,
|
|
16
|
+
supportsAgents
|
|
17
|
+
} from '../config/ai-agent-paths.js';
|
|
18
|
+
import { computeFileHash } from './hasher.js';
|
|
19
|
+
|
|
20
|
+
// Get the CLI package root directory
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = dirname(__filename);
|
|
23
|
+
const CLI_ROOT = join(__dirname, '..', '..');
|
|
24
|
+
const BUNDLED_DIR = join(CLI_ROOT, 'bundled');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the Agents source directory.
|
|
28
|
+
* Prioritizes bundled directory (npm install), falls back to development path.
|
|
29
|
+
* @returns {string} Path to agents source directory
|
|
30
|
+
*/
|
|
31
|
+
function getAgentsSourceDir() {
|
|
32
|
+
const bundledPath = join(BUNDLED_DIR, 'skills', 'claude-code', 'agents');
|
|
33
|
+
if (existsSync(bundledPath)) {
|
|
34
|
+
return bundledPath;
|
|
35
|
+
}
|
|
36
|
+
// Development environment fallback
|
|
37
|
+
return join(CLI_ROOT, '..', 'skills', 'claude-code', 'agents');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const AGENTS_LOCAL_DIR = getAgentsSourceDir();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get list of available agent names from local directory
|
|
44
|
+
* @returns {string[]} Array of agent names (without .md extension)
|
|
45
|
+
*/
|
|
46
|
+
export function getAvailableAgentNames() {
|
|
47
|
+
if (!existsSync(AGENTS_LOCAL_DIR)) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const NON_AGENT_ITEMS = ['README.md', '.DS_Store', '.manifest.json'];
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
return readdirSync(AGENTS_LOCAL_DIR)
|
|
55
|
+
.filter(file => {
|
|
56
|
+
if (NON_AGENT_ITEMS.includes(file)) return false;
|
|
57
|
+
if (!file.endsWith('.md')) return false;
|
|
58
|
+
const itemPath = join(AGENTS_LOCAL_DIR, file);
|
|
59
|
+
return statSync(itemPath).isFile();
|
|
60
|
+
})
|
|
61
|
+
.map(file => basename(file, '.md'));
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get agent definition content
|
|
69
|
+
* @param {string} agentName - Agent name (without .md)
|
|
70
|
+
* @returns {string|null} Agent content or null if not found
|
|
71
|
+
*/
|
|
72
|
+
export function getAgentContent(agentName) {
|
|
73
|
+
const sourcePath = join(AGENTS_LOCAL_DIR, `${agentName}.md`);
|
|
74
|
+
if (!existsSync(sourcePath)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return readFileSync(sourcePath, 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parse agent frontmatter to extract metadata
|
|
82
|
+
* @param {string} content - Agent file content
|
|
83
|
+
* @returns {Object} Parsed frontmatter fields
|
|
84
|
+
*/
|
|
85
|
+
export function parseAgentFrontmatter(content) {
|
|
86
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
87
|
+
if (!frontmatterMatch) {
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const frontmatter = {};
|
|
92
|
+
const lines = frontmatterMatch[1].split('\n');
|
|
93
|
+
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
const match = line.match(/^(\w[\w-]*):\s*(.+)$/);
|
|
96
|
+
if (match) {
|
|
97
|
+
const [, key, value] = match;
|
|
98
|
+
// Handle arrays (simple case)
|
|
99
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
100
|
+
frontmatter[key] = value.slice(1, -1).split(',').map(s => s.trim().replace(/"/g, ''));
|
|
101
|
+
} else {
|
|
102
|
+
frontmatter[key] = value.trim();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return frontmatter;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Install agents for a specific AI tool
|
|
112
|
+
* @param {string} agent - AI tool identifier (e.g., 'claude-code', 'opencode')
|
|
113
|
+
* @param {string} level - 'user' or 'project'
|
|
114
|
+
* @param {string[]} agentNames - Array of agent names to install (null = all)
|
|
115
|
+
* @param {string} projectPath - Project root path (required for project level)
|
|
116
|
+
* @returns {Object} Installation result
|
|
117
|
+
*/
|
|
118
|
+
export async function installAgentsForTool(agent, level, agentNames = null, projectPath = null) {
|
|
119
|
+
const config = getAgentConfig(agent);
|
|
120
|
+
if (!config || !supportsAgents(agent)) {
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
agent,
|
|
124
|
+
level,
|
|
125
|
+
error: `AI tool '${agent}' does not support agents installation`,
|
|
126
|
+
installed: [],
|
|
127
|
+
errors: []
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Get target directory
|
|
132
|
+
const targetDir = getAgentsDirForAgent(agent, level, projectPath);
|
|
133
|
+
if (!targetDir) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
agent,
|
|
137
|
+
level,
|
|
138
|
+
error: `Could not determine target directory for ${agent} at ${level} level`,
|
|
139
|
+
installed: [],
|
|
140
|
+
errors: []
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Ensure target directory exists
|
|
145
|
+
if (!existsSync(targetDir)) {
|
|
146
|
+
mkdirSync(targetDir, { recursive: true });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Get agents to install
|
|
150
|
+
const availableAgents = getAvailableAgentNames();
|
|
151
|
+
const toInstall = agentNames || availableAgents;
|
|
152
|
+
|
|
153
|
+
const results = {
|
|
154
|
+
success: true,
|
|
155
|
+
agent,
|
|
156
|
+
level,
|
|
157
|
+
targetDir,
|
|
158
|
+
installed: [],
|
|
159
|
+
errors: [],
|
|
160
|
+
fileHashes: {}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
for (const agentName of toInstall) {
|
|
164
|
+
const result = installSingleAgent(agentName, targetDir, agent);
|
|
165
|
+
if (result.success) {
|
|
166
|
+
results.installed.push(agentName);
|
|
167
|
+
} else {
|
|
168
|
+
results.errors.push({ agent: agentName, error: result.error });
|
|
169
|
+
results.success = false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Write manifest
|
|
174
|
+
if (results.installed.length > 0) {
|
|
175
|
+
writeAgentsManifest(agent, level, targetDir, results.installed);
|
|
176
|
+
|
|
177
|
+
// Compute file hashes for tracking
|
|
178
|
+
const now = new Date().toISOString();
|
|
179
|
+
for (const agentName of results.installed) {
|
|
180
|
+
const filePath = join(targetDir, `${agentName}.md`);
|
|
181
|
+
const hashInfo = computeFileHash(filePath);
|
|
182
|
+
if (hashInfo) {
|
|
183
|
+
results.fileHashes[`${agent}/${agentName}.md`] = {
|
|
184
|
+
...hashInfo,
|
|
185
|
+
installedAt: now
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return results;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Install a single agent to target directory
|
|
196
|
+
* @param {string} agentName - Agent name (without .md)
|
|
197
|
+
* @param {string} targetDir - Target directory
|
|
198
|
+
* @param {string} aiTool - AI tool identifier (for potential transformation)
|
|
199
|
+
* @returns {Object} Result
|
|
200
|
+
*/
|
|
201
|
+
function installSingleAgent(agentName, targetDir, aiTool) {
|
|
202
|
+
const sourcePath = join(AGENTS_LOCAL_DIR, `${agentName}.md`);
|
|
203
|
+
const targetPath = join(targetDir, `${agentName}.md`);
|
|
204
|
+
|
|
205
|
+
if (!existsSync(sourcePath)) {
|
|
206
|
+
return {
|
|
207
|
+
success: false,
|
|
208
|
+
agent: agentName,
|
|
209
|
+
error: `Agent not found: ${agentName}`
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
let content = readFileSync(sourcePath, 'utf-8');
|
|
215
|
+
|
|
216
|
+
// Transform content if needed for specific AI tools
|
|
217
|
+
content = transformAgentForTool(content, agentName, aiTool);
|
|
218
|
+
|
|
219
|
+
writeFileSync(targetPath, content);
|
|
220
|
+
return { success: true, agent: agentName, path: targetPath };
|
|
221
|
+
} catch (error) {
|
|
222
|
+
return {
|
|
223
|
+
success: false,
|
|
224
|
+
agent: agentName,
|
|
225
|
+
error: error.message
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Transform agent content for a specific AI tool if needed
|
|
232
|
+
* @param {string} content - Original agent content
|
|
233
|
+
* @param {string} agentName - Agent name
|
|
234
|
+
* @param {string} aiTool - AI tool identifier
|
|
235
|
+
* @returns {string} Transformed content
|
|
236
|
+
*/
|
|
237
|
+
function transformAgentForTool(content, agentName, aiTool) {
|
|
238
|
+
// Claude Code and OpenCode use the same format (supports Task tool)
|
|
239
|
+
if (aiTool === 'claude-code' || aiTool === 'opencode' || aiTool === 'roo-code') {
|
|
240
|
+
return content;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// For tools that don't support Task tool, add inline mode header
|
|
244
|
+
// This helps the tool understand to inject the agent as context
|
|
245
|
+
const inlineModeHeader = `<!-- UDS Agent: ${agentName} -->
|
|
246
|
+
<!-- Execution Mode: inline (inject as context) -->
|
|
247
|
+
|
|
248
|
+
`;
|
|
249
|
+
|
|
250
|
+
// Check if content already has the header
|
|
251
|
+
if (content.includes('<!-- UDS Agent:')) {
|
|
252
|
+
return content;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return inlineModeHeader + content;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Write agents manifest
|
|
260
|
+
* @param {string} aiTool - AI tool identifier
|
|
261
|
+
* @param {string} level - 'user' or 'project'
|
|
262
|
+
* @param {string} targetDir - Target directory
|
|
263
|
+
* @param {string[]} agents - List of installed agents
|
|
264
|
+
*/
|
|
265
|
+
function writeAgentsManifest(aiTool, level, targetDir, agents) {
|
|
266
|
+
const manifestPath = join(targetDir, '.manifest.json');
|
|
267
|
+
const { version } = JSON.parse(
|
|
268
|
+
readFileSync(join(CLI_ROOT, 'package.json'), 'utf-8')
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const manifest = {
|
|
272
|
+
version,
|
|
273
|
+
source: 'universal-dev-standards',
|
|
274
|
+
type: 'agents',
|
|
275
|
+
aiTool,
|
|
276
|
+
level,
|
|
277
|
+
agents,
|
|
278
|
+
installedDate: new Date().toISOString().split('T')[0]
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get installed agents info for an AI tool
|
|
286
|
+
* @param {string} aiTool - AI tool identifier
|
|
287
|
+
* @param {string} level - 'user' or 'project'
|
|
288
|
+
* @param {string} projectPath - Project root path (required for project level)
|
|
289
|
+
* @returns {Object|null} Installed agents info or null
|
|
290
|
+
*/
|
|
291
|
+
export function getInstalledAgentsForTool(aiTool, level = 'project', projectPath = null) {
|
|
292
|
+
const targetDir = getAgentsDirForAgent(aiTool, level, projectPath);
|
|
293
|
+
if (!targetDir || !existsSync(targetDir)) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const manifestPath = join(targetDir, '.manifest.json');
|
|
298
|
+
|
|
299
|
+
// Count agent files
|
|
300
|
+
let agentFiles = [];
|
|
301
|
+
try {
|
|
302
|
+
agentFiles = readdirSync(targetDir)
|
|
303
|
+
.filter(f => f.endsWith('.md') && f !== 'README.md');
|
|
304
|
+
} catch {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (agentFiles.length === 0) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const getAgentName = (filename) => basename(filename, '.md');
|
|
313
|
+
|
|
314
|
+
if (!existsSync(manifestPath)) {
|
|
315
|
+
return {
|
|
316
|
+
installed: true,
|
|
317
|
+
count: agentFiles.length,
|
|
318
|
+
agents: agentFiles.map(getAgentName),
|
|
319
|
+
version: null,
|
|
320
|
+
aiTool,
|
|
321
|
+
level,
|
|
322
|
+
path: targetDir
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
328
|
+
return {
|
|
329
|
+
installed: true,
|
|
330
|
+
count: agentFiles.length,
|
|
331
|
+
agents: manifest.agents || agentFiles.map(getAgentName),
|
|
332
|
+
version: manifest.version || null,
|
|
333
|
+
aiTool,
|
|
334
|
+
level,
|
|
335
|
+
path: targetDir,
|
|
336
|
+
installedDate: manifest.installedDate || null
|
|
337
|
+
};
|
|
338
|
+
} catch {
|
|
339
|
+
return {
|
|
340
|
+
installed: true,
|
|
341
|
+
count: agentFiles.length,
|
|
342
|
+
agents: agentFiles.map(getAgentName),
|
|
343
|
+
version: null,
|
|
344
|
+
aiTool,
|
|
345
|
+
level,
|
|
346
|
+
path: targetDir
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Install agents to multiple AI tools at once
|
|
353
|
+
* @param {Array<{agent: string, level: string}>} installations - Array of installation targets
|
|
354
|
+
* @param {string[]} agentNames - Agents to install (null = all)
|
|
355
|
+
* @param {string} projectPath - Project root path
|
|
356
|
+
* @returns {Object} Combined results
|
|
357
|
+
*/
|
|
358
|
+
export async function installAgentsToMultipleTools(installations, agentNames = null, projectPath = null) {
|
|
359
|
+
const results = {
|
|
360
|
+
success: true,
|
|
361
|
+
installations: [],
|
|
362
|
+
totalInstalled: 0,
|
|
363
|
+
totalErrors: 0,
|
|
364
|
+
allFileHashes: {}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
for (const { agent, level } of installations) {
|
|
368
|
+
const result = await installAgentsForTool(agent, level, agentNames, projectPath);
|
|
369
|
+
results.installations.push(result);
|
|
370
|
+
|
|
371
|
+
if (!result.success) {
|
|
372
|
+
results.success = false;
|
|
373
|
+
}
|
|
374
|
+
results.totalInstalled += result.installed.length;
|
|
375
|
+
results.totalErrors += result.errors.length;
|
|
376
|
+
|
|
377
|
+
// Merge file hashes from this installation
|
|
378
|
+
if (result.fileHashes) {
|
|
379
|
+
Object.assign(results.allFileHashes, result.fileHashes);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return results;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export default {
|
|
387
|
+
installAgentsForTool,
|
|
388
|
+
getInstalledAgentsForTool,
|
|
389
|
+
installAgentsToMultipleTools,
|
|
390
|
+
getAvailableAgentNames,
|
|
391
|
+
getAgentContent,
|
|
392
|
+
parseAgentFrontmatter
|
|
393
|
+
};
|