atris 2.3.4 → 2.3.5
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/atris/skills/writing/SKILL.md +1 -1
- package/bin/atris.js +12 -1
- package/commands/member.js +461 -0
- package/commands/workflow.js +9 -3
- package/package.json +1 -1
package/bin/atris.js
CHANGED
|
@@ -225,6 +225,12 @@ function showHelp() {
|
|
|
225
225
|
console.log(' skill audit [name] - Validate skill against Anthropic guide');
|
|
226
226
|
console.log(' skill fix [name] - Auto-fix common compliance issues');
|
|
227
227
|
console.log('');
|
|
228
|
+
console.log('Team:');
|
|
229
|
+
console.log(' member create <name> - Scaffold a new team member (MEMBER.md)');
|
|
230
|
+
console.log(' member list - Show all team members');
|
|
231
|
+
console.log(' member activate <n> - Activate a member (link skills, show context)');
|
|
232
|
+
console.log(' member upgrade <n> - Convert flat file to directory format');
|
|
233
|
+
console.log('');
|
|
228
234
|
console.log('Plugin:');
|
|
229
235
|
console.log(' plugin build - Package skills as .plugin for Cowork');
|
|
230
236
|
console.log(' plugin publish - Sync skills to marketplace repo and push');
|
|
@@ -336,12 +342,13 @@ const { analyticsAtris: analyticsCmd } = require('../commands/analytics');
|
|
|
336
342
|
const { cleanAtris: cleanCmd } = require('../commands/clean');
|
|
337
343
|
const { verifyAtris: verifyCmd } = require('../commands/verify');
|
|
338
344
|
const { skillCommand: skillCmd } = require('../commands/skill');
|
|
345
|
+
const { memberCommand: memberCmd } = require('../commands/member');
|
|
339
346
|
const { pluginCommand: pluginCmd } = require('../commands/plugin');
|
|
340
347
|
|
|
341
348
|
// Check if this is a known command or natural language input
|
|
342
349
|
const knownCommands = ['init', 'log', 'status', 'analytics', 'visualize', 'brainstorm', 'autopilot', 'plan', 'do', 'review',
|
|
343
350
|
'activate', 'agent', 'chat', 'login', 'logout', 'whoami', 'switch', 'accounts', 'update', 'upgrade', 'version', 'help', 'next', 'atris',
|
|
344
|
-
'clean', 'verify', 'search', 'skill', 'plugin',
|
|
351
|
+
'clean', 'verify', 'search', 'skill', 'member', 'plugin',
|
|
345
352
|
'gmail', 'calendar', 'twitter', 'slack', 'integrations'];
|
|
346
353
|
|
|
347
354
|
// Check if command is an atris.md spec file - triggers welcome visualization
|
|
@@ -827,6 +834,10 @@ if (command === 'init') {
|
|
|
827
834
|
const subcommand = process.argv[3];
|
|
828
835
|
const args = process.argv.slice(4);
|
|
829
836
|
skillCmd(subcommand, ...args);
|
|
837
|
+
} else if (command === 'member') {
|
|
838
|
+
const subcommand = process.argv[3];
|
|
839
|
+
const args = process.argv.slice(4);
|
|
840
|
+
memberCmd(subcommand, ...args);
|
|
830
841
|
} else if (command === 'plugin') {
|
|
831
842
|
const subcommand = process.argv[3] || 'build';
|
|
832
843
|
const args = process.argv.slice(4);
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// --- YAML Frontmatter Parser (shared with skill.js) ---
|
|
5
|
+
|
|
6
|
+
function parseFrontmatter(content) {
|
|
7
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
8
|
+
if (!match) return null;
|
|
9
|
+
|
|
10
|
+
const yaml = match[1];
|
|
11
|
+
const result = {};
|
|
12
|
+
let currentKey = null;
|
|
13
|
+
|
|
14
|
+
for (const line of yaml.split('\n')) {
|
|
15
|
+
const listMatch = line.match(/^\s+-\s+(.+)$/);
|
|
16
|
+
if (listMatch && currentKey) {
|
|
17
|
+
if (!Array.isArray(result[currentKey])) result[currentKey] = [];
|
|
18
|
+
result[currentKey].push(listMatch[1].trim());
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const nestedMatch = line.match(/^\s+([a-z_-]+):\s*(.*)$/);
|
|
23
|
+
if (nestedMatch && currentKey && typeof result[currentKey] === 'object' && !Array.isArray(result[currentKey])) {
|
|
24
|
+
const val = nestedMatch[2].trim();
|
|
25
|
+
result[currentKey][nestedMatch[1]] = val === 'true' ? true : val === 'false' ? false : val || true;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const kvMatch = line.match(/^([a-z_-]+):\s*(.*)$/);
|
|
30
|
+
if (kvMatch) {
|
|
31
|
+
currentKey = kvMatch[1];
|
|
32
|
+
const val = kvMatch[2].trim();
|
|
33
|
+
if (val === '') {
|
|
34
|
+
result[currentKey] = {};
|
|
35
|
+
} else if (val.startsWith('[') && val.endsWith(']')) {
|
|
36
|
+
result[currentKey] = val.slice(1, -1).split(',').map(s => s.trim().replace(/^["']|["']$/g, ''));
|
|
37
|
+
} else {
|
|
38
|
+
result[currentKey] = val.replace(/^["']|["']$/g, '');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --- Member Discovery ---
|
|
47
|
+
|
|
48
|
+
function findAllMembers(teamDir) {
|
|
49
|
+
if (!fs.existsSync(teamDir)) return [];
|
|
50
|
+
|
|
51
|
+
const members = [];
|
|
52
|
+
const entries = fs.readdirSync(teamDir);
|
|
53
|
+
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
const fullPath = path.join(teamDir, entry);
|
|
56
|
+
const stat = fs.statSync(fullPath);
|
|
57
|
+
|
|
58
|
+
// Directory format: team/<name>/MEMBER.md
|
|
59
|
+
if (stat.isDirectory()) {
|
|
60
|
+
const memberFile = path.join(fullPath, 'MEMBER.md');
|
|
61
|
+
if (fs.existsSync(memberFile)) {
|
|
62
|
+
const content = fs.readFileSync(memberFile, 'utf8');
|
|
63
|
+
const fm = parseFrontmatter(content) || {};
|
|
64
|
+
|
|
65
|
+
// Count local skills
|
|
66
|
+
const skillsDir = path.join(fullPath, 'skills');
|
|
67
|
+
let skillCount = 0;
|
|
68
|
+
if (fs.existsSync(skillsDir)) {
|
|
69
|
+
const skillEntries = fs.readdirSync(skillsDir);
|
|
70
|
+
for (const s of skillEntries) {
|
|
71
|
+
if (fs.existsSync(path.join(skillsDir, s, 'SKILL.md'))) skillCount++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Count context files
|
|
76
|
+
const contextDir = path.join(fullPath, 'context');
|
|
77
|
+
let contextCount = 0;
|
|
78
|
+
if (fs.existsSync(contextDir)) {
|
|
79
|
+
contextCount = fs.readdirSync(contextDir).filter(f => f.endsWith('.md')).length;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check for tools
|
|
83
|
+
const toolsDir = path.join(fullPath, 'tools');
|
|
84
|
+
const hasTools = fs.existsSync(toolsDir);
|
|
85
|
+
|
|
86
|
+
members.push({
|
|
87
|
+
name: fm.name || entry,
|
|
88
|
+
role: fm.role || '(no role)',
|
|
89
|
+
description: fm.description || '',
|
|
90
|
+
version: fm.version || '',
|
|
91
|
+
format: 'directory',
|
|
92
|
+
path: memberFile,
|
|
93
|
+
dir: fullPath,
|
|
94
|
+
skillCount,
|
|
95
|
+
contextCount,
|
|
96
|
+
hasTools,
|
|
97
|
+
skills: Array.isArray(fm.skills) ? fm.skills : [],
|
|
98
|
+
permissions: fm.permissions || {},
|
|
99
|
+
frontmatter: fm
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Flat file format: team/<name>.md
|
|
106
|
+
if (entry.endsWith('.md') && stat.isFile()) {
|
|
107
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
108
|
+
const fm = parseFrontmatter(content);
|
|
109
|
+
if (!fm) continue; // No frontmatter = not a member
|
|
110
|
+
|
|
111
|
+
const name = entry.replace('.md', '');
|
|
112
|
+
members.push({
|
|
113
|
+
name: fm.name || name,
|
|
114
|
+
role: fm.role || '(no role)',
|
|
115
|
+
description: fm.description || '',
|
|
116
|
+
version: fm.version || '',
|
|
117
|
+
format: 'flat',
|
|
118
|
+
path: fullPath,
|
|
119
|
+
dir: path.dirname(fullPath),
|
|
120
|
+
skillCount: 0,
|
|
121
|
+
contextCount: 0,
|
|
122
|
+
hasTools: false,
|
|
123
|
+
skills: Array.isArray(fm.skills) ? fm.skills : [],
|
|
124
|
+
permissions: fm.permissions || {},
|
|
125
|
+
frontmatter: fm
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return members;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// --- LIST subcommand ---
|
|
134
|
+
|
|
135
|
+
function memberList() {
|
|
136
|
+
const teamDir = path.join(process.cwd(), 'atris', 'team');
|
|
137
|
+
const members = findAllMembers(teamDir);
|
|
138
|
+
|
|
139
|
+
if (members.length === 0) {
|
|
140
|
+
console.log('No team members found in atris/team/.');
|
|
141
|
+
console.log('Run "atris member create <name>" to create one.');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log('');
|
|
146
|
+
console.log('Team Members');
|
|
147
|
+
console.log('─'.repeat(70));
|
|
148
|
+
|
|
149
|
+
const nameW = 16;
|
|
150
|
+
const roleW = 16;
|
|
151
|
+
const fmtW = 6;
|
|
152
|
+
const skillW = 8;
|
|
153
|
+
const ctxW = 8;
|
|
154
|
+
|
|
155
|
+
console.log(
|
|
156
|
+
' ' +
|
|
157
|
+
'Name'.padEnd(nameW) +
|
|
158
|
+
'Role'.padEnd(roleW) +
|
|
159
|
+
'Format'.padEnd(fmtW) +
|
|
160
|
+
'Skills'.padEnd(skillW) +
|
|
161
|
+
'Context'.padEnd(ctxW) +
|
|
162
|
+
'Version'
|
|
163
|
+
);
|
|
164
|
+
console.log(' ' + '─'.repeat(66));
|
|
165
|
+
|
|
166
|
+
for (const m of members) {
|
|
167
|
+
const skills = m.format === 'directory' ? String(m.skillCount) : '-';
|
|
168
|
+
const context = m.format === 'directory' ? String(m.contextCount) : '-';
|
|
169
|
+
console.log(
|
|
170
|
+
' ' +
|
|
171
|
+
m.name.padEnd(nameW) +
|
|
172
|
+
m.role.substring(0, roleW - 1).padEnd(roleW) +
|
|
173
|
+
(m.format === 'directory' ? 'dir' : 'flat').padEnd(fmtW) +
|
|
174
|
+
skills.padEnd(skillW) +
|
|
175
|
+
context.padEnd(ctxW) +
|
|
176
|
+
(m.version || '-')
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log('');
|
|
181
|
+
console.log(`${members.length} member(s) found.`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// --- CREATE subcommand ---
|
|
185
|
+
|
|
186
|
+
function memberCreate(name, ...flags) {
|
|
187
|
+
if (!name) {
|
|
188
|
+
console.error('Usage: atris member create <name> [--role="Title"]');
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Parse flags
|
|
193
|
+
let role = name.charAt(0).toUpperCase() + name.slice(1).replace(/-/g, ' ');
|
|
194
|
+
let description = '';
|
|
195
|
+
|
|
196
|
+
for (const flag of flags) {
|
|
197
|
+
const roleMatch = flag.match(/^--role=["']?(.+?)["']?$/);
|
|
198
|
+
if (roleMatch) role = roleMatch[1];
|
|
199
|
+
|
|
200
|
+
const descMatch = flag.match(/^--description=["']?(.+?)["']?$/);
|
|
201
|
+
if (descMatch) description = descMatch[1];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const teamDir = path.join(process.cwd(), 'atris', 'team');
|
|
205
|
+
const memberDir = path.join(teamDir, name);
|
|
206
|
+
const memberFile = path.join(memberDir, 'MEMBER.md');
|
|
207
|
+
const legacyFile = path.join(teamDir, `${name}.md`);
|
|
208
|
+
|
|
209
|
+
// Check for existing
|
|
210
|
+
if (fs.existsSync(memberFile)) {
|
|
211
|
+
console.error(`Member "${name}" already exists at team/${name}/MEMBER.md`);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
if (fs.existsSync(legacyFile)) {
|
|
215
|
+
console.error(`Member "${name}" already exists at team/${name}.md`);
|
|
216
|
+
console.log(`Run "atris member upgrade ${name}" to convert to directory format.`);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Scaffold
|
|
221
|
+
fs.mkdirSync(memberDir, { recursive: true });
|
|
222
|
+
fs.mkdirSync(path.join(memberDir, 'skills'), { recursive: true });
|
|
223
|
+
fs.mkdirSync(path.join(memberDir, 'tools'), { recursive: true });
|
|
224
|
+
fs.mkdirSync(path.join(memberDir, 'context'), { recursive: true });
|
|
225
|
+
|
|
226
|
+
const content = `---
|
|
227
|
+
name: ${name}
|
|
228
|
+
role: ${role}
|
|
229
|
+
description: ${description || `Handles ${role.toLowerCase()} tasks`}
|
|
230
|
+
version: 1.0.0
|
|
231
|
+
|
|
232
|
+
skills: []
|
|
233
|
+
|
|
234
|
+
permissions:
|
|
235
|
+
can-read: true
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
# ${role}
|
|
239
|
+
|
|
240
|
+
## Persona
|
|
241
|
+
|
|
242
|
+
(Define how this member communicates, their tone, and decision-making style)
|
|
243
|
+
|
|
244
|
+
## Workflow
|
|
245
|
+
|
|
246
|
+
1. Step one
|
|
247
|
+
2. Step two
|
|
248
|
+
3. Step three
|
|
249
|
+
|
|
250
|
+
## Rules
|
|
251
|
+
|
|
252
|
+
1. Rule one
|
|
253
|
+
2. Rule two
|
|
254
|
+
`;
|
|
255
|
+
|
|
256
|
+
fs.writeFileSync(memberFile, content);
|
|
257
|
+
|
|
258
|
+
console.log('');
|
|
259
|
+
console.log(`✓ Created team/${name}/MEMBER.md`);
|
|
260
|
+
console.log(`✓ Created team/${name}/skills/`);
|
|
261
|
+
console.log(`✓ Created team/${name}/tools/`);
|
|
262
|
+
console.log(`✓ Created team/${name}/context/`);
|
|
263
|
+
console.log('');
|
|
264
|
+
console.log(`Next: edit team/${name}/MEMBER.md to define persona, workflow, and permissions.`);
|
|
265
|
+
console.log(` add skills to team/${name}/skills/<skill-name>/SKILL.md`);
|
|
266
|
+
console.log(` add context docs to team/${name}/context/`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// --- ACTIVATE subcommand ---
|
|
270
|
+
|
|
271
|
+
function memberActivate(name) {
|
|
272
|
+
if (!name) {
|
|
273
|
+
console.error('Usage: atris member activate <name>');
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const teamDir = path.join(process.cwd(), 'atris', 'team');
|
|
278
|
+
const memberDir = path.join(teamDir, name);
|
|
279
|
+
const memberFile = path.join(memberDir, 'MEMBER.md');
|
|
280
|
+
const legacyFile = path.join(teamDir, `${name}.md`);
|
|
281
|
+
|
|
282
|
+
// Find the member (directory first, flat fallback)
|
|
283
|
+
let activePath = null;
|
|
284
|
+
let activeDir = null;
|
|
285
|
+
let isLegacy = false;
|
|
286
|
+
|
|
287
|
+
if (fs.existsSync(memberFile)) {
|
|
288
|
+
activePath = memberFile;
|
|
289
|
+
activeDir = memberDir;
|
|
290
|
+
} else if (fs.existsSync(legacyFile)) {
|
|
291
|
+
activePath = legacyFile;
|
|
292
|
+
activeDir = teamDir;
|
|
293
|
+
isLegacy = true;
|
|
294
|
+
} else {
|
|
295
|
+
console.error(`Member "${name}" not found. Run "atris member list".`);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const content = fs.readFileSync(activePath, 'utf8');
|
|
300
|
+
const fm = parseFrontmatter(content) || {};
|
|
301
|
+
|
|
302
|
+
console.log('');
|
|
303
|
+
console.log(`Activating: ${fm.name || name} (${fm.role || 'no role'})`);
|
|
304
|
+
|
|
305
|
+
// If legacy format, offer upgrade
|
|
306
|
+
if (isLegacy) {
|
|
307
|
+
console.log(` Format: flat file (team/${name}.md)`);
|
|
308
|
+
console.log(` Tip: run "atris member upgrade ${name}" to convert to directory format.`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Symlink member's local skills to system-level
|
|
312
|
+
if (!isLegacy) {
|
|
313
|
+
const skillsDir = path.join(activeDir, 'skills');
|
|
314
|
+
if (fs.existsSync(skillsDir)) {
|
|
315
|
+
const home = require('os').homedir();
|
|
316
|
+
const toolDirs = [
|
|
317
|
+
{ dir: path.join(home, '.claude', 'skills'), label: 'Claude' },
|
|
318
|
+
{ dir: path.join(home, '.codex', 'skills'), label: 'Codex' },
|
|
319
|
+
{ dir: path.join(home, '.cursor', 'skills'), label: 'Cursor' },
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
for (const { dir } of toolDirs) {
|
|
323
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const skillEntries = fs.readdirSync(skillsDir);
|
|
327
|
+
let linked = 0;
|
|
328
|
+
|
|
329
|
+
for (const entry of skillEntries) {
|
|
330
|
+
const skillDir = path.join(skillsDir, entry);
|
|
331
|
+
if (!fs.statSync(skillDir).isDirectory()) continue;
|
|
332
|
+
if (!fs.existsSync(path.join(skillDir, 'SKILL.md'))) continue;
|
|
333
|
+
|
|
334
|
+
for (const { dir, label } of toolDirs) {
|
|
335
|
+
const linkPath = path.join(dir, entry);
|
|
336
|
+
if (fs.existsSync(linkPath)) continue;
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
fs.symlinkSync(skillDir, linkPath);
|
|
340
|
+
} catch (e) { /* silent */ }
|
|
341
|
+
}
|
|
342
|
+
linked++;
|
|
343
|
+
console.log(` ✓ Linked skill: ${entry}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (linked === 0) {
|
|
347
|
+
console.log(' No local skills to link.');
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Show context files
|
|
352
|
+
const contextDir = path.join(activeDir, 'context');
|
|
353
|
+
if (fs.existsSync(contextDir)) {
|
|
354
|
+
const ctxFiles = fs.readdirSync(contextDir).filter(f => f.endsWith('.md'));
|
|
355
|
+
if (ctxFiles.length > 0) {
|
|
356
|
+
console.log(` Context: ${ctxFiles.join(', ')}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Show tools
|
|
361
|
+
const toolsDir = path.join(activeDir, 'tools');
|
|
362
|
+
if (fs.existsSync(toolsDir)) {
|
|
363
|
+
const toolFiles = fs.readdirSync(toolsDir);
|
|
364
|
+
if (toolFiles.length > 0) {
|
|
365
|
+
console.log(` Tools: ${toolFiles.join(', ')}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Show permissions
|
|
371
|
+
if (fm.permissions && typeof fm.permissions === 'object') {
|
|
372
|
+
const perms = Object.entries(fm.permissions);
|
|
373
|
+
if (perms.length > 0) {
|
|
374
|
+
const allowed = perms.filter(([, v]) => v === true || v === 'true').map(([k]) => k);
|
|
375
|
+
const denied = perms.filter(([, v]) => v === false || v === 'false').map(([k]) => k);
|
|
376
|
+
if (allowed.length) console.log(` Allowed: ${allowed.join(', ')}`);
|
|
377
|
+
if (denied.length) console.log(` Denied: ${denied.join(', ')}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
console.log('');
|
|
382
|
+
console.log(`Member "${fm.name || name}" activated.`);
|
|
383
|
+
console.log(`Tell your agent: "You are the ${fm.role || name}. Read team/${name}/MEMBER.md."`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// --- UPGRADE subcommand ---
|
|
387
|
+
|
|
388
|
+
function memberUpgrade(name) {
|
|
389
|
+
if (!name) {
|
|
390
|
+
console.error('Usage: atris member upgrade <name>');
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const teamDir = path.join(process.cwd(), 'atris', 'team');
|
|
395
|
+
const legacyFile = path.join(teamDir, `${name}.md`);
|
|
396
|
+
const memberDir = path.join(teamDir, name);
|
|
397
|
+
const memberFile = path.join(memberDir, 'MEMBER.md');
|
|
398
|
+
|
|
399
|
+
if (!fs.existsSync(legacyFile)) {
|
|
400
|
+
if (fs.existsSync(memberFile)) {
|
|
401
|
+
console.log(`"${name}" is already in directory format.`);
|
|
402
|
+
} else {
|
|
403
|
+
console.error(`Member "${name}" not found at team/${name}.md`);
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (fs.existsSync(memberDir)) {
|
|
409
|
+
console.error(`Directory team/${name}/ already exists. Resolve manually.`);
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Move flat file to directory format
|
|
414
|
+
fs.mkdirSync(memberDir, { recursive: true });
|
|
415
|
+
fs.mkdirSync(path.join(memberDir, 'skills'), { recursive: true });
|
|
416
|
+
fs.mkdirSync(path.join(memberDir, 'tools'), { recursive: true });
|
|
417
|
+
fs.mkdirSync(path.join(memberDir, 'context'), { recursive: true });
|
|
418
|
+
fs.renameSync(legacyFile, memberFile);
|
|
419
|
+
|
|
420
|
+
console.log(`✓ Upgraded team/${name}.md → team/${name}/MEMBER.md`);
|
|
421
|
+
console.log(`✓ Created skills/, tools/, context/ directories`);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// --- Command Dispatcher ---
|
|
425
|
+
|
|
426
|
+
function memberCommand(subcommand, ...args) {
|
|
427
|
+
switch (subcommand) {
|
|
428
|
+
case 'list':
|
|
429
|
+
case 'ls':
|
|
430
|
+
return memberList();
|
|
431
|
+
case 'create':
|
|
432
|
+
case 'new':
|
|
433
|
+
return memberCreate(args[0], ...args.slice(1));
|
|
434
|
+
case 'activate':
|
|
435
|
+
return memberActivate(args[0]);
|
|
436
|
+
case 'upgrade':
|
|
437
|
+
return memberUpgrade(args[0]);
|
|
438
|
+
default:
|
|
439
|
+
console.log('');
|
|
440
|
+
console.log('Usage: atris member <subcommand> [name]');
|
|
441
|
+
console.log('');
|
|
442
|
+
console.log('Subcommands:');
|
|
443
|
+
console.log(' create <name> Scaffold a new team member (MEMBER.md + dirs)');
|
|
444
|
+
console.log(' list Show all team members');
|
|
445
|
+
console.log(' activate <name> Symlink member skills, show context and permissions');
|
|
446
|
+
console.log(' upgrade <name> Convert flat file (name.md) to directory format');
|
|
447
|
+
console.log('');
|
|
448
|
+
console.log('Create flags:');
|
|
449
|
+
console.log(' --role="Title" Set the member role');
|
|
450
|
+
console.log(' --description="..." Set the member description');
|
|
451
|
+
console.log('');
|
|
452
|
+
console.log('Examples:');
|
|
453
|
+
console.log(' atris member create sdr --role="Sales Development Rep"');
|
|
454
|
+
console.log(' atris member list');
|
|
455
|
+
console.log(' atris member activate navigator');
|
|
456
|
+
console.log(' atris member upgrade executor');
|
|
457
|
+
console.log('');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
module.exports = { memberCommand, findAllMembers, parseFrontmatter };
|
package/commands/workflow.js
CHANGED
|
@@ -14,7 +14,9 @@ async function planAtris(userInput = null) {
|
|
|
14
14
|
const executionMode = executeFlag ? 'agent' : (config.execution_mode || 'prompt');
|
|
15
15
|
|
|
16
16
|
const targetDir = path.join(process.cwd(), 'atris');
|
|
17
|
-
const navigatorFile = path.join(targetDir, 'team', 'navigator.md')
|
|
17
|
+
const navigatorFile = fs.existsSync(path.join(targetDir, 'team', 'navigator', 'MEMBER.md'))
|
|
18
|
+
? path.join(targetDir, 'team', 'navigator', 'MEMBER.md')
|
|
19
|
+
: path.join(targetDir, 'team', 'navigator.md');
|
|
18
20
|
const personaPath = path.join(targetDir, 'PERSONA.md');
|
|
19
21
|
const mapFilePath = path.join(targetDir, 'MAP.md');
|
|
20
22
|
const featuresReadmePath = path.join(targetDir, 'features', 'README.md');
|
|
@@ -311,7 +313,9 @@ async function doAtris() {
|
|
|
311
313
|
|
|
312
314
|
const cwd = process.cwd();
|
|
313
315
|
const targetDir = path.join(cwd, 'atris');
|
|
314
|
-
const executorFile = path.join(targetDir, 'team', 'executor.md')
|
|
316
|
+
const executorFile = fs.existsSync(path.join(targetDir, 'team', 'executor', 'MEMBER.md'))
|
|
317
|
+
? path.join(targetDir, 'team', 'executor', 'MEMBER.md')
|
|
318
|
+
: path.join(targetDir, 'team', 'executor.md');
|
|
315
319
|
|
|
316
320
|
if (!fs.existsSync(executorFile)) {
|
|
317
321
|
console.log('✗ executor.md not found. Run "atris init" first.');
|
|
@@ -680,7 +684,9 @@ async function reviewAtris() {
|
|
|
680
684
|
const executionMode = executeFlag ? 'agent' : (config.execution_mode || 'prompt');
|
|
681
685
|
|
|
682
686
|
const targetDir = path.join(process.cwd(), 'atris');
|
|
683
|
-
const validatorFile = path.join(targetDir, 'team', 'validator.md')
|
|
687
|
+
const validatorFile = fs.existsSync(path.join(targetDir, 'team', 'validator', 'MEMBER.md'))
|
|
688
|
+
? path.join(targetDir, 'team', 'validator', 'MEMBER.md')
|
|
689
|
+
: path.join(targetDir, 'team', 'validator.md');
|
|
684
690
|
|
|
685
691
|
if (!fs.existsSync(validatorFile)) {
|
|
686
692
|
console.log('✗ validator.md not found. Run "atris init" first.');
|
package/package.json
CHANGED