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.
@@ -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 (FEATURES/*.md files)
22
- if (fs.existsSync(featuresDir)) {
23
- const featureFiles = fs.readdirSync(featuresDir).filter(f => f.endsWith('.md'));
24
- const inProgressFeatures = featureFiles.filter(f => {
25
- const content = fs.readFileSync(path.join(featuresDir, f), 'utf8');
26
- return content.includes('status: in-progress');
27
- });
28
-
29
- if (inProgressFeatures.length > 0) {
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
- context.map = fs.readFileSync(mapFile, 'utf8').split('\n').slice(0, 5); // First 5 lines
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
- context.hasInbox = true;
114
- context.inboxItems = inboxMatch[1]
115
- .split('\n')
116
- .filter(line => line.trim().startsWith('-'))
117
- .map(line => line.replace(/^-\s*/, '').trim())
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
- if (fs.existsSync(featuresDir)) {
126
- const featureFiles = fs.readdirSync(featuresDir).filter(f => f.endsWith('.md'));
127
- context.inProgressFeatures = featureFiles
128
- .filter(f => {
129
- const content = fs.readFileSync(path.join(featuresDir, f), 'utf8');
130
- return content.includes('status: in-progress');
131
- })
132
- .slice(0, 2); // First 2 features
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.5",
4
- "description": "atrisDev - A new way to build and manage agents",
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": "echo \"Error: no test specified\" && exit 1"
25
+ "test": "node --test"
23
26
  },
24
27
  "keywords": [
25
- "ai",
26
- "agents",
27
- "codebase",
28
- "documentation",
29
- "automation"
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",