atris 2.4.0 → 2.5.1
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/AGENTS.md +1 -2
- package/atris/atris.md +40 -6
- package/atris/features/_templates/changelog.md.template +11 -0
- package/atris/skills/clawhub/chief-of-staff/SKILL.md +65 -0
- package/atris/skills/clawhub/member-runtime/SKILL.md +112 -0
- package/atris/skills/create-app/SKILL.md +278 -0
- package/atris/skills/drive/SKILL.md +20 -0
- package/atris/skills/github/SKILL.md +207 -0
- package/atris/skills/youtube/SKILL.md +207 -0
- package/atris/team/brainstormer/MEMBER.md +23 -0
- package/atris/team/executor/MEMBER.md +29 -0
- package/atris/team/launcher/MEMBER.md +24 -0
- package/atris/team/navigator/MEMBER.md +28 -11
- package/atris/team/navigator/journal/2026-02-23.md +6 -0
- package/atris/team/researcher/MEMBER.md +21 -0
- package/atris/team/validator/MEMBER.md +29 -8
- package/bin/atris.js +241 -1776
- package/commands/analytics.js +2 -2
- package/commands/brainstorm.js +1 -2
- package/commands/member.js +194 -8
- package/commands/status.js +128 -143
- package/commands/sync.js +4 -4
- package/commands/workflow.js +36 -33
- package/lib/state-detection.js +15 -4
- package/lib/todo.js +184 -0
- package/package.json +1 -1
package/commands/workflow.js
CHANGED
|
@@ -167,8 +167,11 @@ async function planAtris(userInput = null) {
|
|
|
167
167
|
}
|
|
168
168
|
console.log('Workflow:');
|
|
169
169
|
console.log('1) ASCII visualize + wait for approval');
|
|
170
|
-
console.log('2) Write
|
|
171
|
-
console.log('
|
|
170
|
+
console.log('2) Write tasks to atris/TODO.md under ## Backlog');
|
|
171
|
+
console.log(' Format: - **T#:** Description [explore|execute]');
|
|
172
|
+
console.log('3) Log to atris/team/navigator/journal/YYYY-MM-DD.md');
|
|
173
|
+
console.log(' (Task, Delivered, User reaction, Pattern)');
|
|
174
|
+
console.log('4) Stop. Do NOT execute (run `atris do` to build).');
|
|
172
175
|
console.log('');
|
|
173
176
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
174
177
|
console.log('💡 After planning: Run "atris do" to execute the build');
|
|
@@ -231,14 +234,15 @@ async function planAtris(userInput = null) {
|
|
|
231
234
|
userPrompt += ` - Include file:line references from MAP.md\n`;
|
|
232
235
|
userPrompt += ` - List dependencies between tasks\n`;
|
|
233
236
|
userPrompt += ` - Add acceptance criteria for each task\n\n`;
|
|
234
|
-
userPrompt += `STEP 3: Write tasks to TODO.md\n`;
|
|
235
|
-
userPrompt += ` - Add
|
|
236
|
-
userPrompt += ` - Format:
|
|
237
|
-
userPrompt += ` -
|
|
238
|
-
userPrompt += `
|
|
239
|
-
userPrompt += `
|
|
240
|
-
userPrompt += ` -
|
|
241
|
-
userPrompt += ` - Include
|
|
237
|
+
userPrompt += `STEP 3: Write tasks to atris/TODO.md\n`;
|
|
238
|
+
userPrompt += ` - Add to ## Backlog section\n`;
|
|
239
|
+
userPrompt += ` - Format: - **T#:** Description [explore|execute]\n`;
|
|
240
|
+
userPrompt += ` - Each task: one job, clear exit condition\n`;
|
|
241
|
+
userPrompt += ` - Include file:line references from MAP.md\n\n`;
|
|
242
|
+
userPrompt += `STEP 4: Log to your journal\n`;
|
|
243
|
+
userPrompt += ` - Write to atris/team/navigator/journal/YYYY-MM-DD.md\n`;
|
|
244
|
+
userPrompt += ` - Include: Task, Delivered, User reaction, Pattern\n`;
|
|
245
|
+
userPrompt += ` - Your journal is how you learn — record what worked\n\n`;
|
|
242
246
|
userPrompt += `Start planning now. Read MAP.md for file references.`;
|
|
243
247
|
|
|
244
248
|
console.log('');
|
|
@@ -478,9 +482,12 @@ async function doAtris() {
|
|
|
478
482
|
console.log('');
|
|
479
483
|
}
|
|
480
484
|
console.log('Workflow:');
|
|
481
|
-
console.log('1)
|
|
482
|
-
console.log('
|
|
483
|
-
console.log('
|
|
485
|
+
console.log('1) Read atris/TODO.md — claim next unclaimed Backlog task');
|
|
486
|
+
console.log(' Move to ## In Progress: add "Claimed by: executor at YYYY-MM-DD HH:MM"');
|
|
487
|
+
console.log('2) Execute step-by-step. Run tests as you go.');
|
|
488
|
+
console.log('3) When done, move task to ## Completed');
|
|
489
|
+
console.log('4) Log to atris/team/executor/journal/YYYY-MM-DD.md');
|
|
490
|
+
console.log(' (Task, Delivered, Errors hit, Learned)');
|
|
484
491
|
console.log('');
|
|
485
492
|
console.log('⛔ Do NOT plan — just execute what\'s written.');
|
|
486
493
|
console.log('');
|
|
@@ -510,12 +517,6 @@ async function doAtris() {
|
|
|
510
517
|
console.log('');
|
|
511
518
|
}
|
|
512
519
|
|
|
513
|
-
if (taskContexts && taskContexts.trim()) {
|
|
514
|
-
console.log('📝 TODO.md (full):');
|
|
515
|
-
console.log('─────────────────────────────────────────────────────────────');
|
|
516
|
-
console.log(taskContexts);
|
|
517
|
-
console.log('');
|
|
518
|
-
}
|
|
519
520
|
}
|
|
520
521
|
|
|
521
522
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
@@ -562,16 +563,13 @@ async function doAtris() {
|
|
|
562
563
|
userPrompt += `## TASKS TO EXECUTE:\n(No tasks found - check TODO.md)\n\n`;
|
|
563
564
|
}
|
|
564
565
|
|
|
565
|
-
if (taskContexts) {
|
|
566
|
-
userPrompt += `## TODO.md (Additional Context):\n${taskContexts}\n\n`;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
566
|
userPrompt += `Your process (EXECUTE these steps):\n`;
|
|
570
567
|
userPrompt += `1. Read tasks from TODO.md (shown above)\n`;
|
|
571
568
|
userPrompt += `2. For each task: Show ASCII visualization first (especially complex changes)\n`;
|
|
572
569
|
userPrompt += `3. Execute task: Use file edit tools, terminal commands, etc.\n`;
|
|
573
|
-
userPrompt += `4.
|
|
574
|
-
userPrompt += `5.
|
|
570
|
+
userPrompt += `4. Move task to ## Completed in TODO.md\n`;
|
|
571
|
+
userPrompt += `5. Log to atris/team/executor/journal/YYYY-MM-DD.md\n`;
|
|
572
|
+
userPrompt += ` (Task, Delivered, Errors hit, Learned)\n`;
|
|
575
573
|
userPrompt += `6. Use MAP.md to navigate codebase\n\n`;
|
|
576
574
|
userPrompt += `DO NOT just describe what you would do - actually edit files and execute commands!\n`;
|
|
577
575
|
userPrompt += `Context: ${context}\n`;
|
|
@@ -818,11 +816,13 @@ async function reviewAtris() {
|
|
|
818
816
|
console.log('Workflow:');
|
|
819
817
|
console.log('1) Run the project test suite (follow TESTING_GUIDE if present).');
|
|
820
818
|
console.log('2) Execute any `atris/features/*/validate.md` scripts; if a step fails, fix + rerun.');
|
|
821
|
-
console.log('3)
|
|
822
|
-
console.log('
|
|
823
|
-
console.log('
|
|
819
|
+
console.log('3) Clean TODO.md: delete completed tasks. Target state = 0.');
|
|
820
|
+
console.log(' If a task fails validation, move back to ## Backlog with note.');
|
|
821
|
+
console.log('4) Log to atris/team/validator/journal/YYYY-MM-DD.md');
|
|
822
|
+
console.log(' (Task, Result, Issues found, Learned)');
|
|
823
|
+
console.log('5) If anything surprised you, append to atris/lessons.md.');
|
|
824
824
|
console.log('');
|
|
825
|
-
console.log('Done when: ✅ All good. Ready for human testing.');
|
|
825
|
+
console.log('Done when: ✅ All good. TODO.md clean. Ready for human testing.');
|
|
826
826
|
console.log('');
|
|
827
827
|
|
|
828
828
|
if (showFull) {
|
|
@@ -909,9 +909,10 @@ async function reviewAtris() {
|
|
|
909
909
|
userPrompt += `Your job:\n`;
|
|
910
910
|
userPrompt += ` • Verify everything works\n`;
|
|
911
911
|
userPrompt += ` • Test thoroughly (unless user says no)\n`;
|
|
912
|
-
userPrompt += ` •
|
|
913
|
-
userPrompt += `
|
|
914
|
-
userPrompt += ` •
|
|
912
|
+
userPrompt += ` • Clean TODO.md — DELETE completed tasks. Target state = 0.\n`;
|
|
913
|
+
userPrompt += ` If a task fails, move it back to ## Backlog with a note.\n`;
|
|
914
|
+
userPrompt += ` • Log to atris/team/validator/journal/YYYY-MM-DD.md\n`;
|
|
915
|
+
userPrompt += ` (Task, Result, Issues found, Learned)\n`;
|
|
915
916
|
userPrompt += ` • If anything surprised you, append to atris/lessons.md\n`;
|
|
916
917
|
userPrompt += ` • EVOLUTION: If you see drift in the logs, propose a tool upgrade.\n\n`;
|
|
917
918
|
userPrompt += `The cycle: do → review → [issues] → do → review → ✅ Ready\n`;
|
|
@@ -996,7 +997,9 @@ async function reviewAtris() {
|
|
|
996
997
|
}
|
|
997
998
|
}
|
|
998
999
|
|
|
999
|
-
// Prompt for learnings
|
|
1000
|
+
// Prompt for learnings (skip if stdin is not a TTY)
|
|
1001
|
+
if (!process.stdin.isTTY) return;
|
|
1002
|
+
|
|
1000
1003
|
console.log('');
|
|
1001
1004
|
console.log('┌─────────────────────────────────────────────────────────────┐');
|
|
1002
1005
|
console.log('│ 💡 Any learnings? │');
|
package/lib/state-detection.js
CHANGED
|
@@ -42,10 +42,21 @@ function getBacklogTasks(atrisDir) {
|
|
|
42
42
|
? todoFile
|
|
43
43
|
: (fs.existsSync(legacyTaskContextFile) ? legacyTaskContextFile : null);
|
|
44
44
|
|
|
45
|
-
if (
|
|
45
|
+
if (taskFilePath) {
|
|
46
|
+
const content = fs.readFileSync(taskFilePath, 'utf8');
|
|
47
|
+
const tasks = getTasksFromTodoSection(content, 'Backlog');
|
|
48
|
+
if (tasks.length > 0) return tasks;
|
|
49
|
+
}
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
// Fallback: check today's journal Backlog section
|
|
52
|
+
const todayLog = getTodayLogPath(path.join(atrisDir, 'logs'));
|
|
53
|
+
if (fs.existsSync(todayLog)) {
|
|
54
|
+
const journalContent = fs.readFileSync(todayLog, 'utf8');
|
|
55
|
+
const journalTasks = getTasksFromTodoSection(journalContent, 'Backlog');
|
|
56
|
+
if (journalTasks.length > 0) return journalTasks;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return [];
|
|
49
60
|
}
|
|
50
61
|
|
|
51
62
|
function getInProgressTasks(atrisDir) {
|
|
@@ -82,7 +93,7 @@ function getTasksFromTodoSection(content, sectionName) {
|
|
|
82
93
|
if (!match) return [];
|
|
83
94
|
|
|
84
95
|
const body = (match[1] || '').trim();
|
|
85
|
-
if (!body || /\(empty/i.test(body)) return [];
|
|
96
|
+
if (!body || /\(empty|\(see /i.test(body)) return [];
|
|
86
97
|
|
|
87
98
|
const tasks = [];
|
|
88
99
|
const titleRegex = /^Task:\s*(.+)$/gim;
|
package/lib/todo.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse TODO.md into structured task objects.
|
|
6
|
+
* Supports format: - **T1:** Description
|
|
7
|
+
* with optional **Claimed by:** and **Stage:** lines
|
|
8
|
+
*/
|
|
9
|
+
function parseTodo(todoPath) {
|
|
10
|
+
if (!fs.existsSync(todoPath)) return { backlog: [], inProgress: [], completed: [] };
|
|
11
|
+
|
|
12
|
+
const content = fs.readFileSync(todoPath, 'utf8');
|
|
13
|
+
return {
|
|
14
|
+
backlog: parseSection(content, 'Backlog'),
|
|
15
|
+
inProgress: parseSection(content, 'In Progress'),
|
|
16
|
+
completed: parseSection(content, 'Completed'),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseSection(content, sectionName) {
|
|
21
|
+
const escaped = sectionName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
22
|
+
const match = content.match(new RegExp(`##\\s+${escaped}\\n([\\s\\S]*?)(?=\\n##|$)`, 'i'));
|
|
23
|
+
if (!match) return [];
|
|
24
|
+
|
|
25
|
+
const body = (match[1] || '').trim();
|
|
26
|
+
if (!body || /^\(clean\)/i.test(body) || /^\(empty/i.test(body) || /^\(see /i.test(body)) return [];
|
|
27
|
+
|
|
28
|
+
const tasks = [];
|
|
29
|
+
const lines = body.split('\n');
|
|
30
|
+
let current = null;
|
|
31
|
+
|
|
32
|
+
for (const rawLine of lines) {
|
|
33
|
+
const line = rawLine.trimEnd();
|
|
34
|
+
|
|
35
|
+
// New task line: - **T1:** Description or - **T1:** Description [tag]
|
|
36
|
+
const taskMatch = line.match(/^- \*\*([A-Z]\d+):\*\*\s*(.+)$/);
|
|
37
|
+
if (taskMatch) {
|
|
38
|
+
if (current) tasks.push(current);
|
|
39
|
+
const tagMatch = taskMatch[2].match(/\[(\w+)\]\s*$/);
|
|
40
|
+
current = {
|
|
41
|
+
id: taskMatch[1],
|
|
42
|
+
title: tagMatch ? taskMatch[2].replace(/\s*\[\w+\]\s*$/, '').trim() : taskMatch[2].trim(),
|
|
43
|
+
tag: tagMatch ? tagMatch[1] : null,
|
|
44
|
+
claimed: null,
|
|
45
|
+
stage: null,
|
|
46
|
+
};
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Also support checkbox format: - [x] Description
|
|
51
|
+
const checkMatch = line.match(/^- \[[ x]\]\s+(.+)$/);
|
|
52
|
+
if (checkMatch && !current) {
|
|
53
|
+
tasks.push({
|
|
54
|
+
id: null,
|
|
55
|
+
title: checkMatch[1].trim(),
|
|
56
|
+
tag: null,
|
|
57
|
+
claimed: null,
|
|
58
|
+
stage: null,
|
|
59
|
+
});
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Plain bullet without ID: - Description
|
|
64
|
+
const plainMatch = line.match(/^- (.+)$/);
|
|
65
|
+
if (plainMatch && !current && !plainMatch[1].startsWith('**')) {
|
|
66
|
+
tasks.push({
|
|
67
|
+
id: null,
|
|
68
|
+
title: plainMatch[1].trim(),
|
|
69
|
+
tag: null,
|
|
70
|
+
claimed: null,
|
|
71
|
+
stage: null,
|
|
72
|
+
});
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!current) continue;
|
|
77
|
+
|
|
78
|
+
// Claimed by line
|
|
79
|
+
const claimMatch = line.match(/\*\*Claimed by:\*\*\s*(.+)$/) || line.match(/Claimed by:\s*(.+)$/);
|
|
80
|
+
if (claimMatch) {
|
|
81
|
+
current.claimed = claimMatch[1].trim();
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Stage line
|
|
86
|
+
const stageMatch = line.match(/\*\*Stage:\*\*\s*(.+)$/) || line.match(/Stage:\s*(.+)$/);
|
|
87
|
+
if (stageMatch) {
|
|
88
|
+
current.stage = stageMatch[1].trim();
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (current) tasks.push(current);
|
|
94
|
+
return tasks;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get latest journal entry for a team member.
|
|
99
|
+
* Reads the most recent file in atris/team/[member]/journal/
|
|
100
|
+
* Returns { date, entries[] } or null
|
|
101
|
+
*/
|
|
102
|
+
function getTeamMemberJournal(atrisDir, memberName) {
|
|
103
|
+
const journalDir = path.join(atrisDir, 'team', memberName, 'journal');
|
|
104
|
+
if (!fs.existsSync(journalDir)) return null;
|
|
105
|
+
|
|
106
|
+
let files;
|
|
107
|
+
try {
|
|
108
|
+
files = fs.readdirSync(journalDir)
|
|
109
|
+
.filter(f => f.endsWith('.md'))
|
|
110
|
+
.sort()
|
|
111
|
+
.reverse();
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (files.length === 0) return null;
|
|
117
|
+
|
|
118
|
+
const latestFile = files[0];
|
|
119
|
+
let content;
|
|
120
|
+
try {
|
|
121
|
+
content = fs.readFileSync(path.join(journalDir, latestFile), 'utf8');
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const date = latestFile.replace('.md', '');
|
|
126
|
+
|
|
127
|
+
// Extract key fields from journal entry
|
|
128
|
+
const taskMatch = content.match(/\*\*Task:\*\*\s*(.+)/);
|
|
129
|
+
const deliveredMatch = content.match(/\*\*Delivered:\*\*\s*(.+)/);
|
|
130
|
+
const patternMatch = content.match(/\*\*Pattern:\*\*\s*(.+)/);
|
|
131
|
+
const learnedMatch = content.match(/\*\*Learned:\*\*\s*(.+)/);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
date,
|
|
135
|
+
file: path.join(journalDir, latestFile),
|
|
136
|
+
task: taskMatch ? taskMatch[1].trim() : null,
|
|
137
|
+
delivered: deliveredMatch ? deliveredMatch[1].trim() : null,
|
|
138
|
+
pattern: patternMatch ? patternMatch[1].trim() : null,
|
|
139
|
+
learned: learnedMatch ? learnedMatch[1].trim() : null,
|
|
140
|
+
raw: content,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get all team members that have directories in atris/team/
|
|
146
|
+
*/
|
|
147
|
+
function listTeamMembers(atrisDir) {
|
|
148
|
+
const teamDir = path.join(atrisDir, 'team');
|
|
149
|
+
if (!fs.existsSync(teamDir)) return [];
|
|
150
|
+
|
|
151
|
+
return fs.readdirSync(teamDir)
|
|
152
|
+
.filter(name => {
|
|
153
|
+
if (name.startsWith('_') || name.startsWith('.')) return false;
|
|
154
|
+
const full = path.join(teamDir, name);
|
|
155
|
+
try { return fs.statSync(full).isDirectory(); } catch { return false; }
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get team activity: latest journal entry per member
|
|
161
|
+
*/
|
|
162
|
+
function getTeamActivity(atrisDir) {
|
|
163
|
+
const members = listTeamMembers(atrisDir);
|
|
164
|
+
const activity = [];
|
|
165
|
+
|
|
166
|
+
for (const member of members) {
|
|
167
|
+
const journal = getTeamMemberJournal(atrisDir, member);
|
|
168
|
+
if (journal) {
|
|
169
|
+
activity.push({ member, ...journal });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Sort by date descending (most recent first)
|
|
174
|
+
activity.sort((a, b) => b.date.localeCompare(a.date));
|
|
175
|
+
return activity;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = {
|
|
179
|
+
parseTodo,
|
|
180
|
+
parseSection,
|
|
181
|
+
getTeamMemberJournal,
|
|
182
|
+
listTeamMembers,
|
|
183
|
+
getTeamActivity,
|
|
184
|
+
};
|
package/package.json
CHANGED