tmux-team 2.2.0 → 3.0.0-alpha.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/README.md +21 -191
- package/package.json +1 -1
- package/src/cli.ts +0 -5
- package/src/commands/config.ts +2 -44
- package/src/commands/help.ts +0 -2
- package/src/commands/talk.test.ts +296 -46
- package/src/commands/talk.ts +69 -63
- package/src/config.test.ts +0 -1
- package/src/config.ts +0 -1
- package/src/identity.ts +89 -0
- package/src/types.ts +2 -2
- package/src/version.ts +1 -1
- package/src/pm/commands.test.ts +0 -1462
- package/src/pm/commands.ts +0 -1011
- package/src/pm/manager.test.ts +0 -377
- package/src/pm/manager.ts +0 -146
- package/src/pm/permissions.test.ts +0 -444
- package/src/pm/permissions.ts +0 -293
- package/src/pm/storage/adapter.ts +0 -57
- package/src/pm/storage/fs.test.ts +0 -512
- package/src/pm/storage/fs.ts +0 -290
- package/src/pm/storage/github.ts +0 -842
- package/src/pm/types.ts +0 -91
package/src/pm/storage/fs.ts
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
// ─────────────────────────────────────────────────────────────
|
|
2
|
-
// Filesystem storage adapter for PM
|
|
3
|
-
// ─────────────────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import type { StorageAdapter } from './adapter.js';
|
|
8
|
-
import type {
|
|
9
|
-
Team,
|
|
10
|
-
Milestone,
|
|
11
|
-
Task,
|
|
12
|
-
AuditEvent,
|
|
13
|
-
CreateTaskInput,
|
|
14
|
-
UpdateTaskInput,
|
|
15
|
-
CreateMilestoneInput,
|
|
16
|
-
UpdateMilestoneInput,
|
|
17
|
-
ListTasksFilter,
|
|
18
|
-
} from '../types.js';
|
|
19
|
-
|
|
20
|
-
export class FSAdapter implements StorageAdapter {
|
|
21
|
-
private teamDir: string;
|
|
22
|
-
|
|
23
|
-
constructor(teamDir: string) {
|
|
24
|
-
this.teamDir = teamDir;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
private get teamFile(): string {
|
|
28
|
-
return path.join(this.teamDir, 'team.json');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
private get milestonesDir(): string {
|
|
32
|
-
return path.join(this.teamDir, 'milestones');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
private get tasksDir(): string {
|
|
36
|
-
return path.join(this.teamDir, 'tasks');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private get eventsFile(): string {
|
|
40
|
-
return path.join(this.teamDir, 'events.jsonl');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
private ensureDir(dir: string): void {
|
|
44
|
-
if (!fs.existsSync(dir)) {
|
|
45
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private readJson<T>(filePath: string): T | null {
|
|
50
|
-
if (!fs.existsSync(filePath)) return null;
|
|
51
|
-
try {
|
|
52
|
-
return JSON.parse(fs.readFileSync(filePath, 'utf-8')) as T;
|
|
53
|
-
} catch {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private writeJson(filePath: string, data: unknown): void {
|
|
59
|
-
this.ensureDir(path.dirname(filePath));
|
|
60
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private now(): string {
|
|
64
|
-
return new Date().toISOString();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
private nextId(dir: string): string {
|
|
68
|
-
this.ensureDir(dir);
|
|
69
|
-
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
70
|
-
const ids = files.map((f) => parseInt(f.replace('.json', ''), 10)).filter((n) => !isNaN(n));
|
|
71
|
-
const max = ids.length > 0 ? Math.max(...ids) : 0;
|
|
72
|
-
return String(max + 1);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Team operations
|
|
76
|
-
async initTeam(name: string, windowId?: string): Promise<Team> {
|
|
77
|
-
this.ensureDir(this.teamDir);
|
|
78
|
-
const team: Team = {
|
|
79
|
-
id: path.basename(this.teamDir),
|
|
80
|
-
name,
|
|
81
|
-
windowId,
|
|
82
|
-
createdAt: this.now(),
|
|
83
|
-
};
|
|
84
|
-
this.writeJson(this.teamFile, team);
|
|
85
|
-
this.ensureDir(this.milestonesDir);
|
|
86
|
-
this.ensureDir(this.tasksDir);
|
|
87
|
-
return team;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async getTeam(): Promise<Team | null> {
|
|
91
|
-
return this.readJson<Team>(this.teamFile);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async updateTeam(updates: Partial<Team>): Promise<Team> {
|
|
95
|
-
const team = await this.getTeam();
|
|
96
|
-
if (!team) throw new Error('Team not initialized');
|
|
97
|
-
const updated = { ...team, ...updates };
|
|
98
|
-
this.writeJson(this.teamFile, updated);
|
|
99
|
-
return updated;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Milestone operations
|
|
103
|
-
async createMilestone(input: CreateMilestoneInput): Promise<Milestone> {
|
|
104
|
-
const id = this.nextId(this.milestonesDir);
|
|
105
|
-
const milestone: Milestone = {
|
|
106
|
-
id,
|
|
107
|
-
name: input.name,
|
|
108
|
-
status: 'pending',
|
|
109
|
-
description: input.description,
|
|
110
|
-
docPath: `milestones/${id}.md`,
|
|
111
|
-
createdAt: this.now(),
|
|
112
|
-
updatedAt: this.now(),
|
|
113
|
-
};
|
|
114
|
-
this.writeJson(path.join(this.milestonesDir, `${id}.json`), milestone);
|
|
115
|
-
// Create doc file with name and optional description
|
|
116
|
-
const docContent = input.description
|
|
117
|
-
? `# ${input.name}\n\n${input.description}\n`
|
|
118
|
-
: `# ${input.name}\n\n`;
|
|
119
|
-
fs.writeFileSync(path.join(this.milestonesDir, `${id}.md`), docContent);
|
|
120
|
-
return milestone;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async getMilestone(id: string): Promise<Milestone | null> {
|
|
124
|
-
return this.readJson<Milestone>(path.join(this.milestonesDir, `${id}.json`));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async listMilestones(): Promise<Milestone[]> {
|
|
128
|
-
this.ensureDir(this.milestonesDir);
|
|
129
|
-
const files = fs.readdirSync(this.milestonesDir).filter((f) => f.endsWith('.json'));
|
|
130
|
-
const milestones: Milestone[] = [];
|
|
131
|
-
for (const file of files) {
|
|
132
|
-
const m = this.readJson<Milestone>(path.join(this.milestonesDir, file));
|
|
133
|
-
if (m) milestones.push(m);
|
|
134
|
-
}
|
|
135
|
-
return milestones.sort((a, b) => parseInt(a.id) - parseInt(b.id));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async updateMilestone(id: string, input: UpdateMilestoneInput): Promise<Milestone> {
|
|
139
|
-
const milestone = await this.getMilestone(id);
|
|
140
|
-
if (!milestone) throw new Error(`Milestone ${id} not found`);
|
|
141
|
-
const updated: Milestone = {
|
|
142
|
-
...milestone,
|
|
143
|
-
...input,
|
|
144
|
-
updatedAt: this.now(),
|
|
145
|
-
};
|
|
146
|
-
this.writeJson(path.join(this.milestonesDir, `${id}.json`), updated);
|
|
147
|
-
return updated;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async deleteMilestone(id: string): Promise<void> {
|
|
151
|
-
const jsonPath = path.join(this.milestonesDir, `${id}.json`);
|
|
152
|
-
const mdPath = path.join(this.milestonesDir, `${id}.md`);
|
|
153
|
-
if (fs.existsSync(jsonPath)) fs.unlinkSync(jsonPath);
|
|
154
|
-
if (fs.existsSync(mdPath)) fs.unlinkSync(mdPath);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Task operations
|
|
158
|
-
async createTask(input: CreateTaskInput): Promise<Task> {
|
|
159
|
-
const id = this.nextId(this.tasksDir);
|
|
160
|
-
const task: Task = {
|
|
161
|
-
id,
|
|
162
|
-
title: input.title,
|
|
163
|
-
milestone: input.milestone,
|
|
164
|
-
status: 'pending',
|
|
165
|
-
assignee: input.assignee,
|
|
166
|
-
docPath: `tasks/${id}.md`,
|
|
167
|
-
createdAt: this.now(),
|
|
168
|
-
updatedAt: this.now(),
|
|
169
|
-
};
|
|
170
|
-
this.writeJson(path.join(this.tasksDir, `${id}.json`), task);
|
|
171
|
-
// Create doc file with title and optional body
|
|
172
|
-
const docContent = input.body ? `# ${input.title}\n\n${input.body}\n` : `# ${input.title}\n\n`;
|
|
173
|
-
fs.writeFileSync(path.join(this.tasksDir, `${id}.md`), docContent);
|
|
174
|
-
return task;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async getTask(id: string): Promise<Task | null> {
|
|
178
|
-
return this.readJson<Task>(path.join(this.tasksDir, `${id}.json`));
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async listTasks(filter?: ListTasksFilter): Promise<Task[]> {
|
|
182
|
-
this.ensureDir(this.tasksDir);
|
|
183
|
-
const files = fs.readdirSync(this.tasksDir).filter((f) => f.endsWith('.json'));
|
|
184
|
-
let tasks: Task[] = [];
|
|
185
|
-
for (const file of files) {
|
|
186
|
-
const t = this.readJson<Task>(path.join(this.tasksDir, file));
|
|
187
|
-
if (t) tasks.push(t);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Apply filters
|
|
191
|
-
if (filter?.milestone) {
|
|
192
|
-
tasks = tasks.filter((t) => t.milestone === filter.milestone);
|
|
193
|
-
}
|
|
194
|
-
if (filter?.status) {
|
|
195
|
-
tasks = tasks.filter((t) => t.status === filter.status);
|
|
196
|
-
}
|
|
197
|
-
if (filter?.assignee) {
|
|
198
|
-
tasks = tasks.filter((t) => t.assignee === filter.assignee);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Exclude tasks in completed milestones (default: true)
|
|
202
|
-
const excludeCompleted = filter?.excludeCompletedMilestones ?? true;
|
|
203
|
-
if (excludeCompleted) {
|
|
204
|
-
const milestones = await this.listMilestones();
|
|
205
|
-
const completedMilestoneIds = new Set(
|
|
206
|
-
milestones.filter((m) => m.status === 'done').map((m) => m.id)
|
|
207
|
-
);
|
|
208
|
-
tasks = tasks.filter((t) => !t.milestone || !completedMilestoneIds.has(t.milestone));
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Hide tasks without milestone if configured
|
|
212
|
-
if (filter?.hideOrphanTasks) {
|
|
213
|
-
tasks = tasks.filter((t) => t.milestone);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return tasks.sort((a, b) => parseInt(a.id) - parseInt(b.id));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async updateTask(id: string, input: UpdateTaskInput): Promise<Task> {
|
|
220
|
-
const task = await this.getTask(id);
|
|
221
|
-
if (!task) throw new Error(`Task ${id} not found`);
|
|
222
|
-
const updated: Task = {
|
|
223
|
-
...task,
|
|
224
|
-
...input,
|
|
225
|
-
updatedAt: this.now(),
|
|
226
|
-
};
|
|
227
|
-
this.writeJson(path.join(this.tasksDir, `${id}.json`), updated);
|
|
228
|
-
return updated;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async deleteTask(id: string): Promise<void> {
|
|
232
|
-
const jsonPath = path.join(this.tasksDir, `${id}.json`);
|
|
233
|
-
const mdPath = path.join(this.tasksDir, `${id}.md`);
|
|
234
|
-
if (fs.existsSync(jsonPath)) fs.unlinkSync(jsonPath);
|
|
235
|
-
if (fs.existsSync(mdPath)) fs.unlinkSync(mdPath);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Documentation
|
|
239
|
-
async getTaskDoc(id: string): Promise<string | null> {
|
|
240
|
-
const docPath = path.join(this.tasksDir, `${id}.md`);
|
|
241
|
-
if (!fs.existsSync(docPath)) return null;
|
|
242
|
-
return fs.readFileSync(docPath, 'utf-8');
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async setTaskDoc(id: string, content: string): Promise<void> {
|
|
246
|
-
const docPath = path.join(this.tasksDir, `${id}.md`);
|
|
247
|
-
fs.writeFileSync(docPath, content);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async getMilestoneDoc(id: string): Promise<string | null> {
|
|
251
|
-
const docPath = path.join(this.milestonesDir, `${id}.md`);
|
|
252
|
-
if (!fs.existsSync(docPath)) return null;
|
|
253
|
-
return fs.readFileSync(docPath, 'utf-8');
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
async setMilestoneDoc(id: string, content: string): Promise<void> {
|
|
257
|
-
this.ensureDir(this.milestonesDir);
|
|
258
|
-
const docPath = path.join(this.milestonesDir, `${id}.md`);
|
|
259
|
-
fs.writeFileSync(docPath, content);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Audit log
|
|
263
|
-
async appendEvent(event: AuditEvent): Promise<void> {
|
|
264
|
-
this.ensureDir(this.teamDir);
|
|
265
|
-
fs.appendFileSync(this.eventsFile, JSON.stringify(event) + '\n', { flag: 'a' });
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async getEvents(limit?: number): Promise<AuditEvent[]> {
|
|
269
|
-
if (!fs.existsSync(this.eventsFile)) return [];
|
|
270
|
-
const lines = fs.readFileSync(this.eventsFile, 'utf-8').trim().split('\n');
|
|
271
|
-
const events: AuditEvent[] = [];
|
|
272
|
-
for (const line of lines) {
|
|
273
|
-
if (line.trim()) {
|
|
274
|
-
try {
|
|
275
|
-
events.push(JSON.parse(line) as AuditEvent);
|
|
276
|
-
} catch {
|
|
277
|
-
// Skip malformed lines
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
if (limit) {
|
|
282
|
-
return events.slice(-limit);
|
|
283
|
-
}
|
|
284
|
-
return events;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
export function createFSAdapter(teamDir: string): StorageAdapter {
|
|
289
|
-
return new FSAdapter(teamDir);
|
|
290
|
-
}
|