atris 2.0.5 → 2.0.7
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 +3 -3
- package/atris/CLAUDE.md +26 -0
- package/atris/GEMINI.md +26 -0
- package/atris/GETTING_STARTED.md +1 -1
- package/atris/atris.md +75 -662
- package/atris/features/README.md +128 -0
- package/atris/features/_templates/build.md.template +124 -0
- package/atris/features/_templates/idea.md.template +78 -0
- package/atris/features/_templates/validate.md.template +38 -0
- package/atris/policies/ANTISLOP.md +90 -23
- package/atris/policies/atris-backend.md +60 -0
- package/atris/policies/atris-design.md +114 -0
- package/atris.md +75 -662
- package/bin/atris.js +154 -73
- package/commands/activate.js +60 -0
- package/commands/brainstorm.js +46 -16
- package/commands/init.js +58 -38
- package/commands/log.js +2 -3
- package/commands/sync.js +9 -1
- package/commands/workflow.js +355 -182
- package/lib/state-detection.js +168 -34
- package/package.json +19 -8
package/lib/state-detection.js
CHANGED
|
@@ -1,6 +1,138 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
|
+
function isFeatureInProgress(ideaContent) {
|
|
5
|
+
if (!ideaContent || typeof ideaContent !== 'string') return false;
|
|
6
|
+
return /\*\*Status:\*\*\s*in-progress\b/i.test(ideaContent) || /\bstatus:\s*in-progress\b/i.test(ideaContent);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function listFeatureDirs(featuresDir) {
|
|
10
|
+
if (!fs.existsSync(featuresDir)) return [];
|
|
11
|
+
return fs
|
|
12
|
+
.readdirSync(featuresDir)
|
|
13
|
+
.filter((name) => {
|
|
14
|
+
if (name.startsWith('_')) return false;
|
|
15
|
+
const full = path.join(featuresDir, name);
|
|
16
|
+
try {
|
|
17
|
+
return fs.statSync(full).isDirectory();
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getInProgressFeatures(featuresDir) {
|
|
25
|
+
const featureDirs = listFeatureDirs(featuresDir);
|
|
26
|
+
const inProgress = [];
|
|
27
|
+
for (const featureName of featureDirs) {
|
|
28
|
+
const ideaPath = path.join(featuresDir, featureName, 'idea.md');
|
|
29
|
+
if (!fs.existsSync(ideaPath)) continue;
|
|
30
|
+
const content = fs.readFileSync(ideaPath, 'utf8');
|
|
31
|
+
if (isFeatureInProgress(content)) {
|
|
32
|
+
inProgress.push(featureName);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return inProgress;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getBacklogTasks(atrisDir) {
|
|
39
|
+
const todoFile = path.join(atrisDir, 'TODO.md');
|
|
40
|
+
const legacyTaskContextFile = path.join(atrisDir, 'TASK_CONTEXTS.md');
|
|
41
|
+
const taskFilePath = fs.existsSync(todoFile)
|
|
42
|
+
? todoFile
|
|
43
|
+
: (fs.existsSync(legacyTaskContextFile) ? legacyTaskContextFile : null);
|
|
44
|
+
|
|
45
|
+
if (!taskFilePath) return [];
|
|
46
|
+
|
|
47
|
+
const content = fs.readFileSync(taskFilePath, 'utf8');
|
|
48
|
+
return getTasksFromTodoSection(content, 'Backlog');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getInProgressTasks(atrisDir) {
|
|
52
|
+
const todoFile = path.join(atrisDir, 'TODO.md');
|
|
53
|
+
const legacyTaskContextFile = path.join(atrisDir, 'TASK_CONTEXTS.md');
|
|
54
|
+
const taskFilePath = fs.existsSync(todoFile)
|
|
55
|
+
? todoFile
|
|
56
|
+
: (fs.existsSync(legacyTaskContextFile) ? legacyTaskContextFile : null);
|
|
57
|
+
|
|
58
|
+
if (!taskFilePath) return [];
|
|
59
|
+
|
|
60
|
+
const content = fs.readFileSync(taskFilePath, 'utf8');
|
|
61
|
+
return getTasksFromTodoSection(content, 'In Progress');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getCompletedTasks(atrisDir) {
|
|
65
|
+
const todoFile = path.join(atrisDir, 'TODO.md');
|
|
66
|
+
const legacyTaskContextFile = path.join(atrisDir, 'TASK_CONTEXTS.md');
|
|
67
|
+
const taskFilePath = fs.existsSync(todoFile)
|
|
68
|
+
? todoFile
|
|
69
|
+
: (fs.existsSync(legacyTaskContextFile) ? legacyTaskContextFile : null);
|
|
70
|
+
|
|
71
|
+
if (!taskFilePath) return [];
|
|
72
|
+
|
|
73
|
+
const content = fs.readFileSync(taskFilePath, 'utf8');
|
|
74
|
+
return getTasksFromTodoSection(content, 'Completed');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getTasksFromTodoSection(content, sectionName) {
|
|
78
|
+
if (!content || typeof content !== 'string') return [];
|
|
79
|
+
if (!sectionName || typeof sectionName !== 'string') return [];
|
|
80
|
+
|
|
81
|
+
const match = content.match(new RegExp(`##\\s+${escapeRegExp(sectionName)}\\n([\\s\\S]*?)(?=\\n##|$)`, 'i'));
|
|
82
|
+
if (!match) return [];
|
|
83
|
+
|
|
84
|
+
const body = (match[1] || '').trim();
|
|
85
|
+
if (!body || /\(empty/i.test(body)) return [];
|
|
86
|
+
|
|
87
|
+
const tasks = [];
|
|
88
|
+
const titleRegex = /^Task:\s*(.+)$/gim;
|
|
89
|
+
let titleMatch;
|
|
90
|
+
while ((titleMatch = titleRegex.exec(body))) {
|
|
91
|
+
tasks.push(titleMatch[1].trim());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (tasks.length > 0) return tasks;
|
|
95
|
+
|
|
96
|
+
return body
|
|
97
|
+
.split('\n')
|
|
98
|
+
.map((line) => line.trim())
|
|
99
|
+
.filter((line) => /^-\s+/.test(line) && !/\(empty/i.test(line))
|
|
100
|
+
.map((line) => line.replace(/^-+\s*/, '').trim());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function escapeRegExp(value) {
|
|
104
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getTodayInboxItems(workspaceDir) {
|
|
108
|
+
const atrisDir = path.join(workspaceDir, 'atris');
|
|
109
|
+
const logsDir = path.join(atrisDir, 'logs');
|
|
110
|
+
const todayLog = getTodayLogPath(logsDir);
|
|
111
|
+
if (!fs.existsSync(todayLog)) {
|
|
112
|
+
return { count: 0, preview: [], file: todayLog };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const logContent = fs.readFileSync(todayLog, 'utf8');
|
|
116
|
+
const inboxMatch = logContent.match(/## Inbox\n([\s\S]*?)(?=\n##|$)/);
|
|
117
|
+
if (!inboxMatch || !inboxMatch[1] || !inboxMatch[1].trim()) {
|
|
118
|
+
return { count: 0, preview: [], file: todayLog };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const items = inboxMatch[1]
|
|
122
|
+
.split('\n')
|
|
123
|
+
.map((line) => line.trim())
|
|
124
|
+
.filter((line) => line.startsWith('-'))
|
|
125
|
+
.map((line) => line.replace(/^-+\s*/, '').trim())
|
|
126
|
+
.filter(Boolean)
|
|
127
|
+
.filter((line) => !/^\(empty/i.test(line));
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
count: items.length,
|
|
131
|
+
preview: items.slice(0, 3),
|
|
132
|
+
file: todayLog,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
4
136
|
/**
|
|
5
137
|
* Detect workspace state: fresh install, has inbox items, in-progress work, or blocked
|
|
6
138
|
*/
|
|
@@ -18,22 +150,15 @@ function detectWorkspaceState(workspaceDir) {
|
|
|
18
150
|
return { state: 'fresh', reason: 'No atris/ folder found' };
|
|
19
151
|
}
|
|
20
152
|
|
|
21
|
-
// Check for in-progress work (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const feature = inProgressFeatures[0];
|
|
31
|
-
return {
|
|
32
|
-
state: 'in-progress',
|
|
33
|
-
feature: feature.replace('.md', ''),
|
|
34
|
-
file: path.join(featuresDir, feature)
|
|
35
|
-
};
|
|
36
|
-
}
|
|
153
|
+
// Check for in-progress work (features/*/idea.md)
|
|
154
|
+
const inProgressFeatures = getInProgressFeatures(featuresDir);
|
|
155
|
+
if (inProgressFeatures.length > 0) {
|
|
156
|
+
const feature = inProgressFeatures[0];
|
|
157
|
+
return {
|
|
158
|
+
state: 'in-progress',
|
|
159
|
+
feature,
|
|
160
|
+
file: path.join(featuresDir, feature, 'idea.md'),
|
|
161
|
+
};
|
|
37
162
|
}
|
|
38
163
|
|
|
39
164
|
// Check for inbox items in today's log
|
|
@@ -94,15 +219,27 @@ function loadContext(workspaceDir) {
|
|
|
94
219
|
|
|
95
220
|
const context = {
|
|
96
221
|
mapExists: fs.existsSync(mapFile),
|
|
222
|
+
mapStatus: 'missing',
|
|
223
|
+
mapPath: path.relative(workspaceDir, mapFile),
|
|
97
224
|
hasInbox: false,
|
|
98
225
|
inboxItems: [],
|
|
226
|
+
inboxCount: 0,
|
|
99
227
|
inProgressFeatures: [],
|
|
228
|
+
inProgressFeaturesCount: 0,
|
|
229
|
+
backlogTasks: [],
|
|
230
|
+
inProgressTasks: [],
|
|
231
|
+
completedTasks: [],
|
|
100
232
|
recentLog: null
|
|
101
233
|
};
|
|
102
234
|
|
|
103
235
|
// Load MAP
|
|
104
236
|
if (context.mapExists) {
|
|
105
|
-
|
|
237
|
+
const mapContent = fs.readFileSync(mapFile, 'utf8');
|
|
238
|
+
context.map = mapContent.split('\n').slice(0, 5); // First 5 lines
|
|
239
|
+
const normalized = mapContent.toLowerCase();
|
|
240
|
+
const isPlaceholder = normalized.includes('generated by your ai agent after reading atris.md')
|
|
241
|
+
|| normalized.includes('run your ai agent with atris.md to populate this file');
|
|
242
|
+
context.mapStatus = isPlaceholder ? 'placeholder' : 'ready';
|
|
106
243
|
}
|
|
107
244
|
|
|
108
245
|
// Load inbox
|
|
@@ -110,27 +247,23 @@ function loadContext(workspaceDir) {
|
|
|
110
247
|
const logContent = fs.readFileSync(todayLog, 'utf8');
|
|
111
248
|
const inboxMatch = logContent.match(/## Inbox\n([\s\S]*?)(?=##|$)/);
|
|
112
249
|
if (inboxMatch && inboxMatch[1].trim()) {
|
|
113
|
-
|
|
114
|
-
context.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.filter(line => line.length > 0)
|
|
119
|
-
.slice(0, 3); // First 3 items
|
|
120
|
-
context.recentLog = todayLog;
|
|
250
|
+
const inbox = getTodayInboxItems(workspaceDir);
|
|
251
|
+
context.hasInbox = inbox.count > 0;
|
|
252
|
+
context.inboxItems = inbox.preview;
|
|
253
|
+
context.inboxCount = inbox.count;
|
|
254
|
+
context.recentLog = inbox.file;
|
|
121
255
|
}
|
|
122
256
|
}
|
|
123
257
|
|
|
124
258
|
// Load in-progress features
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
259
|
+
const allInProgressFeatures = getInProgressFeatures(featuresDir);
|
|
260
|
+
context.inProgressFeatures = allInProgressFeatures.slice(0, 2);
|
|
261
|
+
context.inProgressFeaturesCount = allInProgressFeatures.length;
|
|
262
|
+
|
|
263
|
+
// Load tasks (for quick status line)
|
|
264
|
+
context.backlogTasks = getBacklogTasks(atrisDir);
|
|
265
|
+
context.inProgressTasks = getInProgressTasks(atrisDir);
|
|
266
|
+
context.completedTasks = getCompletedTasks(atrisDir);
|
|
134
267
|
|
|
135
268
|
return context;
|
|
136
269
|
}
|
|
@@ -138,5 +271,6 @@ function loadContext(workspaceDir) {
|
|
|
138
271
|
module.exports = {
|
|
139
272
|
detectWorkspaceState,
|
|
140
273
|
loadContext,
|
|
274
|
+
getTodayInboxItems,
|
|
141
275
|
getTodayLogPath
|
|
142
276
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atris",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "atrisDev -
|
|
3
|
+
"version": "2.0.7",
|
|
4
|
+
"description": "atrisDev (atris dev) - CLI for AI coding agents. Works with Claude Code, Cursor, Windsurf. Make any codebase AI-navigable.",
|
|
5
5
|
"main": "bin/atris.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"atris": "bin/atris.js"
|
|
@@ -15,18 +15,29 @@
|
|
|
15
15
|
"atris.md",
|
|
16
16
|
"GETTING_STARTED.md",
|
|
17
17
|
"PERSONA.md",
|
|
18
|
+
"atris/CLAUDE.md",
|
|
19
|
+
"atris/GEMINI.md",
|
|
18
20
|
"atris/agent_team/",
|
|
21
|
+
"atris/features/_templates/",
|
|
19
22
|
"atris/policies/"
|
|
20
23
|
],
|
|
21
24
|
"scripts": {
|
|
22
|
-
"test": "
|
|
25
|
+
"test": "node --test"
|
|
23
26
|
},
|
|
24
27
|
"keywords": [
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
28
|
+
"atrisdev",
|
|
29
|
+
"atris-dev",
|
|
30
|
+
"atris",
|
|
31
|
+
"claude-code",
|
|
32
|
+
"cursor",
|
|
33
|
+
"windsurf",
|
|
34
|
+
"coding-agent",
|
|
35
|
+
"ai-coding-assistant",
|
|
36
|
+
"ai-workflow",
|
|
37
|
+
"developer-tools",
|
|
38
|
+
"cli",
|
|
39
|
+
"codebase-navigation",
|
|
40
|
+
"ai-agents"
|
|
30
41
|
],
|
|
31
42
|
"author": "Keshav Rao (atrislabs)",
|
|
32
43
|
"license": "MIT",
|