@rubytech/taskmaster 1.0.89 → 1.0.90

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,5 +1,5 @@
1
1
  {
2
- "version": "1.0.89",
2
+ "version": "1.0.90",
3
3
  "commit": "d12721761c03ecc5631483c59e1696d0ddba08a3",
4
- "builtAt": "2026-02-21T00:01:17.386Z"
4
+ "builtAt": "2026-02-21T00:10:58.623Z"
5
5
  }
@@ -34,6 +34,22 @@ async function walkDir(dir, files) {
34
34
  const entries = await fs.readdir(dir, { withFileTypes: true });
35
35
  for (const entry of entries) {
36
36
  const full = path.join(dir, entry.name);
37
+ // Symlinks need fs.stat to determine the target type
38
+ if (entry.isSymbolicLink()) {
39
+ try {
40
+ const stat = await fs.stat(full);
41
+ if (stat.isDirectory()) {
42
+ await walkDir(full, files);
43
+ }
44
+ else if (stat.isFile() && entry.name.endsWith(".md")) {
45
+ files.push(full);
46
+ }
47
+ }
48
+ catch {
49
+ // Broken symlink — skip
50
+ }
51
+ continue;
52
+ }
37
53
  if (entry.isDirectory()) {
38
54
  await walkDir(full, files);
39
55
  continue;
@@ -25,6 +25,8 @@ import { createSubsystemLogger } from "../logging/subsystem.js";
25
25
  const log = createSubsystemLogger("memory-layout");
26
26
  /** Shared scope folders symlinked from each agent workspace. */
27
27
  const SHARED_FOLDERS = ["public", "shared"];
28
+ /** Agent-specific folders that belong in per-agent memory, not at the shared root. */
29
+ const AGENT_SPECIFIC_FOLDERS = ["admin", "users", "groups"];
28
30
  /** The relative symlink target from agents/{id}/memory/{scope} → ../../../memory/{scope} */
29
31
  const SYMLINK_TARGET_PREFIX = "../../../memory";
30
32
  async function exists(p) {
@@ -139,6 +141,7 @@ export async function ensureMemoryLayout(workspaceRoot, agentDirs) {
139
141
  await fs.mkdir(path.join(memoryPath, "admin"), { recursive: true });
140
142
  }
141
143
  await fs.mkdir(path.join(memoryPath, "users"), { recursive: true });
144
+ await fs.mkdir(path.join(memoryPath, "groups"), { recursive: true });
142
145
  // Remove admin/ from non-admin agent workspaces (it should never exist there)
143
146
  if (!isAdmin) {
144
147
  const adminPath = path.join(memoryPath, "admin");
@@ -147,13 +150,75 @@ export async function ensureMemoryLayout(workspaceRoot, agentDirs) {
147
150
  await fs.rm(adminPath, { recursive: true }).catch(() => { });
148
151
  }
149
152
  else {
150
- log.warn(`non-admin agent has data in memory/admin/ — moving to shared admin location`);
151
- const sharedAdminPath = path.join(sharedMemoryDir, "admin");
152
- await fs.mkdir(sharedAdminPath, { recursive: true });
153
- await mergeDir(adminPath, sharedAdminPath);
153
+ log.warn(`non-admin agent has data in memory/admin/ — moving to admin agent`);
154
+ // Find admin agent dir and merge there
155
+ const adminAgent = agentDirs.find((a) => a.id.toLowerCase() === "admin" || a.id.toLowerCase().endsWith("-admin"));
156
+ if (adminAgent) {
157
+ const adminMemAdmin = path.join(adminAgent.dir, "memory", "admin");
158
+ await fs.mkdir(adminMemAdmin, { recursive: true });
159
+ await mergeDir(adminPath, adminMemAdmin);
160
+ }
154
161
  await fs.rm(adminPath, { recursive: true }).catch(() => { });
155
162
  }
156
163
  }
157
164
  }
158
165
  }
166
+ // Sweep orphaned agent-specific data from the shared root.
167
+ // After full-symlink migration, admin/, users/, groups/, notes/, and loose files
168
+ // remain at ~/taskmaster/memory/ but are no longer accessible via per-subfolder symlinks.
169
+ // Move them to the admin agent's workspace (most privileged, owns historical data).
170
+ await migrateOrphanedSharedData(sharedMemoryDir, agentDirs);
171
+ }
172
+ /**
173
+ * Move agent-specific data that was left at the shared memory root
174
+ * (from the old full-symlink layout) into the admin agent's workspace.
175
+ *
176
+ * Only `public/` and `shared/` belong at the shared root.
177
+ * Everything else is orphaned and needs to move to a per-agent location.
178
+ */
179
+ async function migrateOrphanedSharedData(sharedMemoryDir, agentDirs) {
180
+ const adminAgent = agentDirs.find((a) => a.id.toLowerCase() === "admin" || a.id.toLowerCase().endsWith("-admin"));
181
+ if (!adminAgent)
182
+ return; // No admin agent — nothing to migrate to
183
+ const adminMemoryPath = path.join(adminAgent.dir, "memory");
184
+ let entries;
185
+ try {
186
+ entries = await fs.readdir(sharedMemoryDir);
187
+ }
188
+ catch {
189
+ return; // Shared dir doesn't exist
190
+ }
191
+ const sharedSet = new Set(SHARED_FOLDERS);
192
+ for (const name of entries) {
193
+ // Skip shared folders — they belong here
194
+ if (sharedSet.has(name))
195
+ continue;
196
+ // Skip .DS_Store and other hidden files
197
+ if (name.startsWith("."))
198
+ continue;
199
+ const srcPath = path.join(sharedMemoryDir, name);
200
+ if (await isDirectory(srcPath)) {
201
+ const destPath = path.join(adminMemoryPath, name);
202
+ if (await isEmptyDir(srcPath)) {
203
+ // Empty orphan dir — just remove it
204
+ await fs.rm(srcPath, { recursive: true }).catch(() => { });
205
+ continue;
206
+ }
207
+ // Merge into admin agent's workspace
208
+ log.info(`migrating orphaned ${name}/ from shared root to admin agent`);
209
+ await mergeDir(srcPath, destPath);
210
+ // Remove source if now empty
211
+ if (await isEmptyDir(srcPath)) {
212
+ await fs.rm(srcPath, { recursive: true }).catch(() => { });
213
+ }
214
+ }
215
+ else if (name.endsWith(".md")) {
216
+ // Loose .md files — move to admin agent's memory root
217
+ const destPath = path.join(adminMemoryPath, name);
218
+ if (!(await exists(destPath))) {
219
+ log.info(`migrating orphaned ${name} from shared root to admin agent`);
220
+ await fs.rename(srcPath, destPath);
221
+ }
222
+ }
223
+ }
159
224
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.0.89",
3
+ "version": "1.0.90",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"