dev-team-mcp-server 0.1.0

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.
Files changed (245) hide show
  1. package/README.md +168 -0
  2. package/dist/cli.d.ts +5 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +127 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/config/permissions.d.ts +26 -0
  7. package/dist/config/permissions.d.ts.map +1 -0
  8. package/dist/config/permissions.js +82 -0
  9. package/dist/config/permissions.js.map +1 -0
  10. package/dist/config/team-config.d.ts +28 -0
  11. package/dist/config/team-config.d.ts.map +1 -0
  12. package/dist/config/team-config.js +101 -0
  13. package/dist/config/team-config.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +1301 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/tools/add-backlog.d.ts +21 -0
  19. package/dist/tools/add-backlog.d.ts.map +1 -0
  20. package/dist/tools/add-backlog.js +96 -0
  21. package/dist/tools/add-backlog.js.map +1 -0
  22. package/dist/tools/approve-plan.d.ts +12 -0
  23. package/dist/tools/approve-plan.d.ts.map +1 -0
  24. package/dist/tools/approve-plan.js +181 -0
  25. package/dist/tools/approve-plan.js.map +1 -0
  26. package/dist/tools/approve-test.d.ts +15 -0
  27. package/dist/tools/approve-test.d.ts.map +1 -0
  28. package/dist/tools/approve-test.js +113 -0
  29. package/dist/tools/approve-test.js.map +1 -0
  30. package/dist/tools/archive-all-tasks.d.ts +9 -0
  31. package/dist/tools/archive-all-tasks.d.ts.map +1 -0
  32. package/dist/tools/archive-all-tasks.js +39 -0
  33. package/dist/tools/archive-all-tasks.js.map +1 -0
  34. package/dist/tools/assign-task.d.ts +31 -0
  35. package/dist/tools/assign-task.d.ts.map +1 -0
  36. package/dist/tools/assign-task.js +285 -0
  37. package/dist/tools/assign-task.js.map +1 -0
  38. package/dist/tools/check-queue.d.ts +18 -0
  39. package/dist/tools/check-queue.d.ts.map +1 -0
  40. package/dist/tools/check-queue.js +129 -0
  41. package/dist/tools/check-queue.js.map +1 -0
  42. package/dist/tools/clear-agent.d.ts +8 -0
  43. package/dist/tools/clear-agent.d.ts.map +1 -0
  44. package/dist/tools/clear-agent.js +52 -0
  45. package/dist/tools/clear-agent.js.map +1 -0
  46. package/dist/tools/compact-agent.d.ts +8 -0
  47. package/dist/tools/compact-agent.d.ts.map +1 -0
  48. package/dist/tools/compact-agent.js +52 -0
  49. package/dist/tools/compact-agent.js.map +1 -0
  50. package/dist/tools/compact-all.d.ts +11 -0
  51. package/dist/tools/compact-all.d.ts.map +1 -0
  52. package/dist/tools/compact-all.js +61 -0
  53. package/dist/tools/compact-all.js.map +1 -0
  54. package/dist/tools/configure-modes.d.ts +19 -0
  55. package/dist/tools/configure-modes.d.ts.map +1 -0
  56. package/dist/tools/configure-modes.js +114 -0
  57. package/dist/tools/configure-modes.js.map +1 -0
  58. package/dist/tools/distribute-tasks.d.ts +40 -0
  59. package/dist/tools/distribute-tasks.d.ts.map +1 -0
  60. package/dist/tools/distribute-tasks.js +276 -0
  61. package/dist/tools/distribute-tasks.js.map +1 -0
  62. package/dist/tools/get-backlog.d.ts +9 -0
  63. package/dist/tools/get-backlog.d.ts.map +1 -0
  64. package/dist/tools/get-backlog.js +65 -0
  65. package/dist/tools/get-backlog.js.map +1 -0
  66. package/dist/tools/get-dashboard.d.ts +15 -0
  67. package/dist/tools/get-dashboard.d.ts.map +1 -0
  68. package/dist/tools/get-dashboard.js +154 -0
  69. package/dist/tools/get-dashboard.js.map +1 -0
  70. package/dist/tools/get-project-context.d.ts +11 -0
  71. package/dist/tools/get-project-context.d.ts.map +1 -0
  72. package/dist/tools/get-project-context.js +84 -0
  73. package/dist/tools/get-project-context.js.map +1 -0
  74. package/dist/tools/health-check.d.ts +21 -0
  75. package/dist/tools/health-check.d.ts.map +1 -0
  76. package/dist/tools/health-check.js +86 -0
  77. package/dist/tools/health-check.js.map +1 -0
  78. package/dist/tools/kill-member.d.ts +15 -0
  79. package/dist/tools/kill-member.d.ts.map +1 -0
  80. package/dist/tools/kill-member.js +89 -0
  81. package/dist/tools/kill-member.js.map +1 -0
  82. package/dist/tools/kill-member.test.d.ts +2 -0
  83. package/dist/tools/kill-member.test.d.ts.map +1 -0
  84. package/dist/tools/kill-member.test.js +228 -0
  85. package/dist/tools/kill-member.test.js.map +1 -0
  86. package/dist/tools/process-approval.d.ts +14 -0
  87. package/dist/tools/process-approval.d.ts.map +1 -0
  88. package/dist/tools/process-approval.js +225 -0
  89. package/dist/tools/process-approval.js.map +1 -0
  90. package/dist/tools/process-skill-candidate.d.ts +14 -0
  91. package/dist/tools/process-skill-candidate.d.ts.map +1 -0
  92. package/dist/tools/process-skill-candidate.js +56 -0
  93. package/dist/tools/process-skill-candidate.js.map +1 -0
  94. package/dist/tools/recall-memory.d.ts +9 -0
  95. package/dist/tools/recall-memory.d.ts.map +1 -0
  96. package/dist/tools/recall-memory.js +97 -0
  97. package/dist/tools/recall-memory.js.map +1 -0
  98. package/dist/tools/reject-plan.d.ts +13 -0
  99. package/dist/tools/reject-plan.d.ts.map +1 -0
  100. package/dist/tools/reject-plan.js +154 -0
  101. package/dist/tools/reject-plan.js.map +1 -0
  102. package/dist/tools/reject-test.d.ts +13 -0
  103. package/dist/tools/reject-test.d.ts.map +1 -0
  104. package/dist/tools/reject-test.js +122 -0
  105. package/dist/tools/reject-test.js.map +1 -0
  106. package/dist/tools/report-skill-candidate.d.ts +12 -0
  107. package/dist/tools/report-skill-candidate.d.ts.map +1 -0
  108. package/dist/tools/report-skill-candidate.js +51 -0
  109. package/dist/tools/report-skill-candidate.js.map +1 -0
  110. package/dist/tools/request-approval.d.ts +13 -0
  111. package/dist/tools/request-approval.d.ts.map +1 -0
  112. package/dist/tools/request-approval.js +53 -0
  113. package/dist/tools/request-approval.js.map +1 -0
  114. package/dist/tools/request-member-decrease.d.ts +15 -0
  115. package/dist/tools/request-member-decrease.d.ts.map +1 -0
  116. package/dist/tools/request-member-decrease.js +100 -0
  117. package/dist/tools/request-member-decrease.js.map +1 -0
  118. package/dist/tools/request-member-increase.d.ts +15 -0
  119. package/dist/tools/request-member-increase.d.ts.map +1 -0
  120. package/dist/tools/request-member-increase.js +81 -0
  121. package/dist/tools/request-member-increase.js.map +1 -0
  122. package/dist/tools/save-memory.d.ts +9 -0
  123. package/dist/tools/save-memory.d.ts.map +1 -0
  124. package/dist/tools/save-memory.js +82 -0
  125. package/dist/tools/save-memory.js.map +1 -0
  126. package/dist/tools/send-task.d.ts +18 -0
  127. package/dist/tools/send-task.d.ts.map +1 -0
  128. package/dist/tools/send-task.js +216 -0
  129. package/dist/tools/send-task.js.map +1 -0
  130. package/dist/tools/set-review-mode.d.ts +13 -0
  131. package/dist/tools/set-review-mode.d.ts.map +1 -0
  132. package/dist/tools/set-review-mode.js +69 -0
  133. package/dist/tools/set-review-mode.js.map +1 -0
  134. package/dist/tools/submit-plan.d.ts +16 -0
  135. package/dist/tools/submit-plan.d.ts.map +1 -0
  136. package/dist/tools/submit-plan.js +151 -0
  137. package/dist/tools/submit-plan.js.map +1 -0
  138. package/dist/tools/submit-test.d.ts +15 -0
  139. package/dist/tools/submit-test.d.ts.map +1 -0
  140. package/dist/tools/submit-test.js +110 -0
  141. package/dist/tools/submit-test.js.map +1 -0
  142. package/dist/tools/update-backlog.d.ts +22 -0
  143. package/dist/tools/update-backlog.d.ts.map +1 -0
  144. package/dist/tools/update-backlog.js +94 -0
  145. package/dist/tools/update-backlog.js.map +1 -0
  146. package/dist/tools/update-project-context.d.ts +9 -0
  147. package/dist/tools/update-project-context.d.ts.map +1 -0
  148. package/dist/tools/update-project-context.js +81 -0
  149. package/dist/tools/update-project-context.js.map +1 -0
  150. package/dist/tools/update-task-status.d.ts +25 -0
  151. package/dist/tools/update-task-status.d.ts.map +1 -0
  152. package/dist/tools/update-task-status.js +160 -0
  153. package/dist/tools/update-task-status.js.map +1 -0
  154. package/dist/tools/watch-queue.d.ts +17 -0
  155. package/dist/tools/watch-queue.d.ts.map +1 -0
  156. package/dist/tools/watch-queue.js +132 -0
  157. package/dist/tools/watch-queue.js.map +1 -0
  158. package/dist/types/memory.d.ts +44 -0
  159. package/dist/types/memory.d.ts.map +1 -0
  160. package/dist/types/memory.js +2 -0
  161. package/dist/types/memory.js.map +1 -0
  162. package/dist/types/message.d.ts +25 -0
  163. package/dist/types/message.d.ts.map +1 -0
  164. package/dist/types/message.js +2 -0
  165. package/dist/types/message.js.map +1 -0
  166. package/dist/types/task.d.ts +113 -0
  167. package/dist/types/task.d.ts.map +1 -0
  168. package/dist/types/task.js +2 -0
  169. package/dist/types/task.js.map +1 -0
  170. package/dist/utils/ansi-table.d.ts +41 -0
  171. package/dist/utils/ansi-table.d.ts.map +1 -0
  172. package/dist/utils/ansi-table.js +139 -0
  173. package/dist/utils/ansi-table.js.map +1 -0
  174. package/dist/utils/court.d.ts +22 -0
  175. package/dist/utils/court.d.ts.map +1 -0
  176. package/dist/utils/court.js +76 -0
  177. package/dist/utils/court.js.map +1 -0
  178. package/dist/utils/court.test.d.ts +2 -0
  179. package/dist/utils/court.test.d.ts.map +1 -0
  180. package/dist/utils/court.test.js +151 -0
  181. package/dist/utils/court.test.js.map +1 -0
  182. package/dist/utils/file-lock.d.ts +4 -0
  183. package/dist/utils/file-lock.d.ts.map +1 -0
  184. package/dist/utils/file-lock.js +59 -0
  185. package/dist/utils/file-lock.js.map +1 -0
  186. package/dist/utils/format.d.ts +7 -0
  187. package/dist/utils/format.d.ts.map +1 -0
  188. package/dist/utils/format.js +33 -0
  189. package/dist/utils/format.js.map +1 -0
  190. package/dist/utils/init-project.d.ts +5 -0
  191. package/dist/utils/init-project.d.ts.map +1 -0
  192. package/dist/utils/init-project.js +89 -0
  193. package/dist/utils/init-project.js.map +1 -0
  194. package/dist/utils/logger.d.ts +16 -0
  195. package/dist/utils/logger.d.ts.map +1 -0
  196. package/dist/utils/logger.js +53 -0
  197. package/dist/utils/logger.js.map +1 -0
  198. package/dist/utils/memory.d.ts +60 -0
  199. package/dist/utils/memory.d.ts.map +1 -0
  200. package/dist/utils/memory.js +403 -0
  201. package/dist/utils/memory.js.map +1 -0
  202. package/dist/utils/permission.d.ts +34 -0
  203. package/dist/utils/permission.d.ts.map +1 -0
  204. package/dist/utils/permission.js +52 -0
  205. package/dist/utils/permission.js.map +1 -0
  206. package/dist/utils/permission.test.d.ts +2 -0
  207. package/dist/utils/permission.test.d.ts.map +1 -0
  208. package/dist/utils/permission.test.js +26 -0
  209. package/dist/utils/permission.test.js.map +1 -0
  210. package/dist/utils/queue.d.ts +55 -0
  211. package/dist/utils/queue.d.ts.map +1 -0
  212. package/dist/utils/queue.js +705 -0
  213. package/dist/utils/queue.js.map +1 -0
  214. package/dist/utils/status-transition.d.ts +57 -0
  215. package/dist/utils/status-transition.d.ts.map +1 -0
  216. package/dist/utils/status-transition.js +94 -0
  217. package/dist/utils/status-transition.js.map +1 -0
  218. package/dist/utils/task-archive.d.ts +33 -0
  219. package/dist/utils/task-archive.d.ts.map +1 -0
  220. package/dist/utils/task-archive.js +233 -0
  221. package/dist/utils/task-archive.js.map +1 -0
  222. package/dist/utils/task-manager.d.ts +67 -0
  223. package/dist/utils/task-manager.d.ts.map +1 -0
  224. package/dist/utils/task-manager.js +225 -0
  225. package/dist/utils/task-manager.js.map +1 -0
  226. package/dist/utils/team-session.d.ts +99 -0
  227. package/dist/utils/team-session.d.ts.map +1 -0
  228. package/dist/utils/team-session.js +585 -0
  229. package/dist/utils/team-session.js.map +1 -0
  230. package/dist/utils/validation.d.ts +40 -0
  231. package/dist/utils/validation.d.ts.map +1 -0
  232. package/dist/utils/validation.js +57 -0
  233. package/dist/utils/validation.js.map +1 -0
  234. package/dist/utils/wezterm.d.ts +23 -0
  235. package/dist/utils/wezterm.d.ts.map +1 -0
  236. package/dist/utils/wezterm.js +189 -0
  237. package/dist/utils/wezterm.js.map +1 -0
  238. package/package.json +60 -0
  239. package/templates/skills/report-template.md +58 -0
  240. package/templates/skills/review-code.md +83 -0
  241. package/templates/skills/review-plan.md +65 -0
  242. package/templates/skills/strict-review.md +83 -0
  243. package/templates/skills/strict-workflow.md +48 -0
  244. package/templates/skills/tdd.md +50 -0
  245. package/templates/start-team-skill.md +61 -0
@@ -0,0 +1,705 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { withFileLock, ensureFileExists } from './file-lock.js';
4
+ import { info, error } from './logger.js';
5
+ import { getAllRoles, getMemberRoles } from '../config/team-config.js';
6
+ import { ensureMemoryStructure } from './memory.js';
7
+ import { formatTimestampJST } from './format.js';
8
+ function getProjectPath() {
9
+ const projectPath = process.env.DEV_TEAM_PROJECT_PATH;
10
+ if (!projectPath) {
11
+ throw new Error('DEV_TEAM_PROJECT_PATH environment variable is not set');
12
+ }
13
+ return projectPath;
14
+ }
15
+ function createDefaultMemberStatus() {
16
+ const status = {
17
+ leader: { status: 'idle', hasReceivedTaskThisSession: false },
18
+ };
19
+ for (const member of getMemberRoles()) {
20
+ status[member] = { status: 'idle', hasReceivedTaskThisSession: false };
21
+ }
22
+ return status;
23
+ }
24
+ export function getDevTeamPath() {
25
+ return path.join(getProjectPath(), '.dev-team');
26
+ }
27
+ export function getQueuePath(role) {
28
+ return path.join(getDevTeamPath(), 'queue', `${role}.json`);
29
+ }
30
+ function getDashboardPath() {
31
+ return path.join(getDevTeamPath(), 'status', 'dashboard.json');
32
+ }
33
+ function getDashboardMdPath() {
34
+ return path.join(getDevTeamPath(), 'status', 'dashboard.md');
35
+ }
36
+ function getApprovalsPath() {
37
+ return path.join(getDevTeamPath(), 'status', 'approvals.json');
38
+ }
39
+ /**
40
+ * タスク種別の短縮ラベルを取得(dashboard.md用)
41
+ */
42
+ function getTaskTypeShort(taskType) {
43
+ const labels = {
44
+ investigation: '調査',
45
+ implementation: '実装',
46
+ review: 'レビュー',
47
+ documentation: 'ドキュメント',
48
+ plan: 'プラン',
49
+ test_plan: 'テスト設計',
50
+ test_implementation: 'テスト実装',
51
+ };
52
+ return labels[taskType ?? 'implementation'];
53
+ }
54
+ export function generateDashboardText(dashboard) {
55
+ const lines = [];
56
+ // Title & Project Info
57
+ lines.push('# Dev Team Dashboard');
58
+ lines.push('');
59
+ lines.push(`- **Project:** ${dashboard.projectName}`);
60
+ lines.push(`- **Phase:** ${dashboard.currentPhase}`);
61
+ lines.push(`- **Updated:** ${formatTimestampJST(dashboard.lastUpdated)}`);
62
+ lines.push('');
63
+ // Tasks summary
64
+ lines.push('## Tasks');
65
+ lines.push('');
66
+ lines.push('| Status | Count |');
67
+ lines.push('|--------|------:|');
68
+ lines.push(`| Pending | ${dashboard.tasks.pending} |`);
69
+ lines.push(`| In Progress | ${dashboard.tasks.inProgress} |`);
70
+ lines.push(`| Blocked | ${dashboard.tasks.blocked} |`);
71
+ lines.push(`| Completed | ${dashboard.tasks.completed} |`);
72
+ lines.push(`| **Total** | **${dashboard.tasks.total}** |`);
73
+ lines.push('');
74
+ // Member status
75
+ lines.push('## Member Status');
76
+ lines.push('');
77
+ lines.push('| Member | Status | Current Task | Last Activity |');
78
+ lines.push('|--------|--------|--------------|---------------|');
79
+ const members = ['leader', ...getMemberRoles()];
80
+ for (const member of members) {
81
+ const status = dashboard.memberStatus[member] ?? { status: 'idle', lastActivity: new Date().toISOString(), currentTask: undefined };
82
+ const currentTask = status.currentTask?.title ?? '-';
83
+ const lastActivity = formatTimestampJST(status.lastActivity);
84
+ lines.push(`| ${member} | ${status.status} | ${currentTask} | ${lastActivity} |`);
85
+ }
86
+ lines.push('');
87
+ // Task list (exclude completed, show up to 5)
88
+ const activeTasks = dashboard.taskList.filter(t => t.status !== 'completed');
89
+ lines.push('## Task List');
90
+ lines.push('');
91
+ if (activeTasks.length === 0) {
92
+ lines.push('*No active tasks.*');
93
+ }
94
+ else {
95
+ const displayTasks = activeTasks.slice(0, 5);
96
+ lines.push('| ID | Task | Assignee | Type | Status | Priority |');
97
+ lines.push('|----|------|----------|------|--------|----------|');
98
+ for (const task of displayTasks) {
99
+ const taskTypeShort = getTaskTypeShort(task.taskType);
100
+ lines.push(`| ${task.id} | ${task.title} | ${task.assignee} | ${taskTypeShort} | ${task.status} | ${task.priority} |`);
101
+ }
102
+ const remaining = activeTasks.length - 5;
103
+ if (remaining > 0) {
104
+ lines.push(`\n*(他 ${remaining} 件)*`);
105
+ }
106
+ }
107
+ lines.push('');
108
+ // Pending approvals
109
+ const pendingApprovals = dashboard.pendingApprovals.filter(a => a.status === 'pending');
110
+ if (pendingApprovals.length > 0) {
111
+ lines.push('## Pending Approvals');
112
+ lines.push('');
113
+ for (const approval of pendingApprovals) {
114
+ lines.push(`- **${approval.title}** (${approval.type})`);
115
+ lines.push(` ${approval.description}`);
116
+ lines.push(` *Requested by:* ${approval.requestedBy} | ${formatTimestampJST(approval.requestedAt)} | \`${approval.id}\``);
117
+ }
118
+ lines.push('');
119
+ }
120
+ // Recent activity
121
+ if (dashboard.recentActivity.length > 0) {
122
+ lines.push('## Recent Activity');
123
+ lines.push('');
124
+ for (const a of dashboard.recentActivity.slice(0, 5)) {
125
+ lines.push(`- \`${formatTimestampJST(a.timestamp)}\` **${a.role}**: ${a.action}`);
126
+ }
127
+ lines.push('');
128
+ }
129
+ return lines.join('\n');
130
+ }
131
+ // Debounce dashboard.md writes to avoid redundant I/O on rapid updates
132
+ let dashWriteTimer = null;
133
+ let pendingDashboard = null;
134
+ async function flushDashboard() {
135
+ if (!pendingDashboard)
136
+ return;
137
+ const dashboard = pendingDashboard;
138
+ pendingDashboard = null;
139
+ try {
140
+ const mdPath = getDashboardMdPath();
141
+ const text = generateDashboardText(dashboard);
142
+ await fs.writeFile(mdPath, text, 'utf-8');
143
+ }
144
+ catch (err) {
145
+ error('Failed to write dashboard.md', err);
146
+ }
147
+ }
148
+ async function writeDashboard(dashboard) {
149
+ pendingDashboard = dashboard;
150
+ if (dashWriteTimer) {
151
+ clearTimeout(dashWriteTimer);
152
+ }
153
+ dashWriteTimer = setTimeout(() => {
154
+ dashWriteTimer = null;
155
+ flushDashboard();
156
+ }, 500);
157
+ }
158
+ export async function ensureDevTeamStructure() {
159
+ const devTeamPath = getDevTeamPath();
160
+ const dirs = [
161
+ path.join(devTeamPath, 'queue'),
162
+ path.join(devTeamPath, 'status'),
163
+ path.join(devTeamPath, 'memory'),
164
+ ...getMemberRoles().map(member => path.join(devTeamPath, 'workspaces', member)),
165
+ ];
166
+ for (const dir of dirs) {
167
+ await fs.mkdir(dir, { recursive: true });
168
+ }
169
+ // Initialize memory structure (memories.jsonl, project.md)
170
+ await ensureMemoryStructure();
171
+ // Initialize queue files for each role
172
+ const roles = getAllRoles();
173
+ for (const role of roles) {
174
+ const queuePath = getQueuePath(role);
175
+ await ensureFileExists(queuePath);
176
+ let needsQueueInit = false;
177
+ try {
178
+ const content = await fs.readFile(queuePath, 'utf-8');
179
+ const parsed = JSON.parse(content);
180
+ // Check if it's a valid MessageQueue (not just empty object)
181
+ if (!parsed.role || !Array.isArray(parsed.messages)) {
182
+ needsQueueInit = true;
183
+ }
184
+ }
185
+ catch {
186
+ needsQueueInit = true;
187
+ }
188
+ if (needsQueueInit) {
189
+ const initialQueue = {
190
+ role,
191
+ messages: [],
192
+ lastUpdated: new Date().toISOString(),
193
+ };
194
+ await fs.writeFile(queuePath, JSON.stringify(initialQueue, null, 2), 'utf-8');
195
+ }
196
+ }
197
+ // Initialize dashboard
198
+ const dashboardPath = getDashboardPath();
199
+ await ensureFileExists(dashboardPath);
200
+ let needsInit = false;
201
+ try {
202
+ const content = await fs.readFile(dashboardPath, 'utf-8');
203
+ const parsed = JSON.parse(content);
204
+ // Check if it's a valid Dashboard (not just empty object)
205
+ if (!parsed.projectName || !parsed.tasks) {
206
+ needsInit = true;
207
+ }
208
+ }
209
+ catch {
210
+ needsInit = true;
211
+ }
212
+ if (needsInit) {
213
+ const initialDashboard = {
214
+ projectName: path.basename(getProjectPath()),
215
+ lastUpdated: new Date().toISOString(),
216
+ currentPhase: 'planning',
217
+ tasks: {
218
+ pending: 0,
219
+ inProgress: 0,
220
+ completed: 0,
221
+ blocked: 0,
222
+ total: 0,
223
+ },
224
+ recentActivity: [],
225
+ pendingApprovals: [],
226
+ memberStatus: createDefaultMemberStatus(),
227
+ taskList: [],
228
+ };
229
+ await fs.writeFile(dashboardPath, JSON.stringify(initialDashboard, null, 2), 'utf-8');
230
+ }
231
+ // Sync dashboard.md
232
+ try {
233
+ const content = await fs.readFile(dashboardPath, 'utf-8');
234
+ const dashboard = parseDashboard(content);
235
+ const txtPath = getDashboardMdPath();
236
+ await fs.writeFile(txtPath, generateDashboardText(dashboard), 'utf-8');
237
+ }
238
+ catch {
239
+ // ignore - dashboard.md will be generated on next getDashboard() call
240
+ }
241
+ info('Dev team structure initialized');
242
+ }
243
+ export async function readQueue(role) {
244
+ const queuePath = getQueuePath(role);
245
+ return withFileLock(queuePath, async () => {
246
+ const content = await fs.readFile(queuePath, 'utf-8');
247
+ return JSON.parse(content);
248
+ });
249
+ }
250
+ export async function addMessage(to, message) {
251
+ const queuePath = getQueuePath(to);
252
+ await withFileLock(queuePath, async () => {
253
+ const content = await fs.readFile(queuePath, 'utf-8');
254
+ const queue = JSON.parse(content);
255
+ queue.messages.push(message);
256
+ queue.lastUpdated = new Date().toISOString();
257
+ await fs.writeFile(queuePath, JSON.stringify(queue, null, 2), 'utf-8');
258
+ });
259
+ info(`Message added to ${to}'s queue`, { messageId: message.id });
260
+ }
261
+ export async function markMessageRead(role, messageId) {
262
+ const queuePath = getQueuePath(role);
263
+ await withFileLock(queuePath, async () => {
264
+ const content = await fs.readFile(queuePath, 'utf-8');
265
+ const queue = JSON.parse(content);
266
+ const message = queue.messages.find(m => m.id === messageId);
267
+ if (message) {
268
+ message.read = true;
269
+ queue.lastUpdated = new Date().toISOString();
270
+ await fs.writeFile(queuePath, JSON.stringify(queue, null, 2), 'utf-8');
271
+ }
272
+ });
273
+ }
274
+ export async function clearReadMessages(role) {
275
+ const queuePath = getQueuePath(role);
276
+ let clearedCount = 0;
277
+ await withFileLock(queuePath, async () => {
278
+ const content = await fs.readFile(queuePath, 'utf-8');
279
+ const queue = JSON.parse(content);
280
+ const originalCount = queue.messages.length;
281
+ queue.messages = queue.messages.filter(m => !m.read);
282
+ clearedCount = originalCount - queue.messages.length;
283
+ queue.lastUpdated = new Date().toISOString();
284
+ await fs.writeFile(queuePath, JSON.stringify(queue, null, 2), 'utf-8');
285
+ });
286
+ return clearedCount;
287
+ }
288
+ /**
289
+ * dashboardファイルからパースし互換補完するヘルパー(ロック外でも利用可)
290
+ */
291
+ function parseDashboard(content) {
292
+ const parsed = JSON.parse(content);
293
+ const tasks = parsed.tasks ?? { pending: 0, inProgress: 0, completed: 0, blocked: 0, total: 0 };
294
+ return {
295
+ ...parsed,
296
+ memberStatus: parsed.memberStatus ?? createDefaultMemberStatus(),
297
+ taskList: parsed.taskList ?? [],
298
+ tasks: {
299
+ ...tasks,
300
+ blocked: tasks.blocked ?? 0,
301
+ },
302
+ };
303
+ }
304
+ export async function getDashboard(options) {
305
+ const dashboardPath = getDashboardPath();
306
+ return withFileLock(dashboardPath, async () => {
307
+ const content = await fs.readFile(dashboardPath, 'utf-8');
308
+ const dashboard = parseDashboard(content);
309
+ // readOnlyでない場合のみMDファイルを同期(I/O削減)
310
+ if (!options?.readOnly) {
311
+ await writeDashboard(dashboard);
312
+ }
313
+ return dashboard;
314
+ });
315
+ }
316
+ export async function updateDashboard(updates, txDashboard) {
317
+ // トランザクション内: インメモリ更新のみ
318
+ if (txDashboard) {
319
+ Object.assign(txDashboard, updates, { lastUpdated: new Date().toISOString() });
320
+ return txDashboard;
321
+ }
322
+ const dashboardPath = getDashboardPath();
323
+ return withFileLock(dashboardPath, async () => {
324
+ const content = await fs.readFile(dashboardPath, 'utf-8');
325
+ const dashboard = parseDashboard(content);
326
+ const updated = {
327
+ ...dashboard,
328
+ ...updates,
329
+ lastUpdated: new Date().toISOString(),
330
+ };
331
+ await fs.writeFile(dashboardPath, JSON.stringify(updated, null, 2), 'utf-8');
332
+ await writeDashboard(updated);
333
+ return updated;
334
+ });
335
+ }
336
+ /**
337
+ * 複数のdashboard変更を1回のロック内でまとめて実行するトランザクション関数。
338
+ * ロックを1回だけ取得し、callbackにdashboardオブジェクトを渡す。
339
+ * callback内で複数の変更を行い、最後に1回だけ書き込む。
340
+ *
341
+ * callback に渡される dashboard は updateTaskInList, updateMemberStatus,
342
+ * updateDashboard, addActivity の txDashboard 引数として利用可能。
343
+ */
344
+ export async function withDashboardTransaction(callback) {
345
+ const dashboardPath = getDashboardPath();
346
+ return withFileLock(dashboardPath, async () => {
347
+ const content = await fs.readFile(dashboardPath, 'utf-8');
348
+ const dashboard = parseDashboard(content);
349
+ const result = await callback(dashboard);
350
+ // 最終書き込み(1回だけ)
351
+ dashboard.lastUpdated = new Date().toISOString();
352
+ await fs.writeFile(dashboardPath, JSON.stringify(dashboard, null, 2), 'utf-8');
353
+ await writeDashboard(dashboard);
354
+ return { result, dashboard };
355
+ });
356
+ }
357
+ export async function addActivity(activity, txDashboard) {
358
+ // トランザクション内: インメモリ更新のみ
359
+ if (txDashboard) {
360
+ txDashboard.recentActivity.unshift({
361
+ ...activity,
362
+ timestamp: new Date().toISOString(),
363
+ });
364
+ txDashboard.recentActivity = txDashboard.recentActivity.slice(0, 50);
365
+ txDashboard.lastUpdated = new Date().toISOString();
366
+ return;
367
+ }
368
+ const dashboardPath = getDashboardPath();
369
+ await withFileLock(dashboardPath, async () => {
370
+ const content = await fs.readFile(dashboardPath, 'utf-8');
371
+ const dashboard = JSON.parse(content);
372
+ dashboard.recentActivity.unshift({
373
+ ...activity,
374
+ timestamp: new Date().toISOString(),
375
+ });
376
+ // Keep only last 50 activities
377
+ dashboard.recentActivity = dashboard.recentActivity.slice(0, 50);
378
+ dashboard.lastUpdated = new Date().toISOString();
379
+ await fs.writeFile(dashboardPath, JSON.stringify(dashboard, null, 2), 'utf-8');
380
+ await writeDashboard(dashboard);
381
+ });
382
+ }
383
+ export async function addApprovalRequest(request) {
384
+ const dashboardPath = getDashboardPath();
385
+ const newRequest = {
386
+ ...request,
387
+ id: `approval-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
388
+ requestedAt: new Date().toISOString(),
389
+ status: 'pending',
390
+ };
391
+ await withFileLock(dashboardPath, async () => {
392
+ const content = await fs.readFile(dashboardPath, 'utf-8');
393
+ const dashboard = JSON.parse(content);
394
+ dashboard.pendingApprovals.push(newRequest);
395
+ dashboard.lastUpdated = new Date().toISOString();
396
+ await fs.writeFile(dashboardPath, JSON.stringify(dashboard, null, 2), 'utf-8');
397
+ await writeDashboard(dashboard);
398
+ });
399
+ return newRequest;
400
+ }
401
+ export async function updateApprovalStatus(approvalId, status, comments) {
402
+ const dashboardPath = getDashboardPath();
403
+ let updatedRequest = null;
404
+ await withFileLock(dashboardPath, async () => {
405
+ const content = await fs.readFile(dashboardPath, 'utf-8');
406
+ const dashboard = JSON.parse(content);
407
+ const request = dashboard.pendingApprovals.find(r => r.id === approvalId);
408
+ if (request) {
409
+ // ステータス更新
410
+ request.status = status;
411
+ request.comments = comments;
412
+ if (status === 'approved') {
413
+ request.approvedAt = new Date().toISOString();
414
+ }
415
+ else {
416
+ request.rejectedAt = new Date().toISOString();
417
+ }
418
+ updatedRequest = request;
419
+ // クリーンアップ: 処理済みエントリを即座に削除(pendingのみ残す)
420
+ dashboard.pendingApprovals = dashboard.pendingApprovals.filter(a => {
421
+ return a.status === 'pending';
422
+ });
423
+ dashboard.lastUpdated = new Date().toISOString();
424
+ await fs.writeFile(dashboardPath, JSON.stringify(dashboard, null, 2), 'utf-8');
425
+ await writeDashboard(dashboard);
426
+ }
427
+ });
428
+ return updatedRequest;
429
+ }
430
+ export async function generateId() {
431
+ const dashboardPath = getDashboardPath();
432
+ let newId = '';
433
+ await withFileLock(dashboardPath, async () => {
434
+ const content = await fs.readFile(dashboardPath, 'utf-8');
435
+ const dashboard = JSON.parse(content);
436
+ // Get next task ID (default to 1 if not set)
437
+ const nextTaskId = dashboard.nextTaskId ?? 1;
438
+ // Generate ID in T-001 format
439
+ newId = `T-${nextTaskId.toString().padStart(3, '0')}`;
440
+ // Increment and save
441
+ dashboard.nextTaskId = nextTaskId + 1;
442
+ dashboard.lastUpdated = new Date().toISOString();
443
+ await fs.writeFile(dashboardPath, JSON.stringify(dashboard, null, 2), 'utf-8');
444
+ });
445
+ return newId;
446
+ }
447
+ /**
448
+ * メッセージID(タスク以外)を生成する
449
+ * タスクIDとは別の形式で、カウンター管理不要
450
+ * Format: M-{timestamp}-{random5chars}
451
+ */
452
+ export function generateMessageId() {
453
+ const timestamp = Date.now();
454
+ const random = Math.random().toString(36).substring(2, 7);
455
+ return `M-${timestamp}-${random}`;
456
+ }
457
+ export async function updateMemberStatus(role, status, txDashboard) {
458
+ // トランザクション内: インメモリ更新のみ
459
+ if (txDashboard) {
460
+ const memberStatus = txDashboard.memberStatus;
461
+ memberStatus[role] = {
462
+ ...memberStatus[role],
463
+ ...status,
464
+ };
465
+ txDashboard.lastUpdated = new Date().toISOString();
466
+ return;
467
+ }
468
+ const dashboardPath = getDashboardPath();
469
+ await withFileLock(dashboardPath, async () => {
470
+ const content = await fs.readFile(dashboardPath, 'utf-8');
471
+ const dashboard = parseDashboard(content);
472
+ const memberStatus = dashboard.memberStatus;
473
+ memberStatus[role] = {
474
+ ...memberStatus[role],
475
+ ...status,
476
+ };
477
+ dashboard.lastUpdated = new Date().toISOString();
478
+ await fs.writeFile(dashboardPath, JSON.stringify(dashboard, null, 2), 'utf-8');
479
+ await writeDashboard(dashboard);
480
+ });
481
+ }
482
+ export async function addTaskToList(task) {
483
+ const dashboardPath = getDashboardPath();
484
+ await withFileLock(dashboardPath, async () => {
485
+ const content = await fs.readFile(dashboardPath, 'utf-8');
486
+ const parsed = JSON.parse(content);
487
+ // 既存データ互換性
488
+ const dashboard = {
489
+ ...parsed,
490
+ memberStatus: parsed.memberStatus ?? createDefaultMemberStatus(),
491
+ taskList: parsed.taskList ?? [],
492
+ };
493
+ dashboard.taskList.push(task);
494
+ // 優先度順 > 作成日時順(新しい順)でソート
495
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
496
+ dashboard.taskList.sort((a, b) => {
497
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
498
+ if (priorityDiff !== 0)
499
+ return priorityDiff;
500
+ return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
501
+ });
502
+ dashboard.lastUpdated = new Date().toISOString();
503
+ await fs.writeFile(dashboardPath, JSON.stringify(dashboard, null, 2), 'utf-8');
504
+ await writeDashboard(dashboard);
505
+ });
506
+ }
507
+ export async function updateTaskInList(taskId, updates, txDashboard) {
508
+ // トランザクション内: インメモリ更新のみ
509
+ if (txDashboard) {
510
+ const taskIndex = txDashboard.taskList.findIndex(t => t.id === taskId);
511
+ if (taskIndex !== -1) {
512
+ txDashboard.taskList[taskIndex] = {
513
+ ...txDashboard.taskList[taskIndex],
514
+ ...updates,
515
+ };
516
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
517
+ txDashboard.taskList.sort((a, b) => {
518
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
519
+ if (priorityDiff !== 0)
520
+ return priorityDiff;
521
+ return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
522
+ });
523
+ txDashboard.lastUpdated = new Date().toISOString();
524
+ info(`Task ${taskId} updated in taskList (tx)`, { updates });
525
+ return true;
526
+ }
527
+ info(`Task ${taskId} not found in taskList (tx, total tasks: ${txDashboard.taskList.length})`);
528
+ return false;
529
+ }
530
+ const dashboardPath = getDashboardPath();
531
+ let updated = false;
532
+ await withFileLock(dashboardPath, async () => {
533
+ const content = await fs.readFile(dashboardPath, 'utf-8');
534
+ const dashboard = parseDashboard(content);
535
+ const taskIndex = dashboard.taskList.findIndex(t => t.id === taskId);
536
+ if (taskIndex !== -1) {
537
+ dashboard.taskList[taskIndex] = {
538
+ ...dashboard.taskList[taskIndex],
539
+ ...updates,
540
+ };
541
+ // 優先度順 > 作成日時順(新しい順)でソート
542
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
543
+ dashboard.taskList.sort((a, b) => {
544
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
545
+ if (priorityDiff !== 0)
546
+ return priorityDiff;
547
+ return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
548
+ });
549
+ dashboard.lastUpdated = new Date().toISOString();
550
+ await fs.writeFile(dashboardPath, JSON.stringify(dashboard, null, 2), 'utf-8');
551
+ await writeDashboard(dashboard);
552
+ updated = true;
553
+ info(`Task ${taskId} updated in taskList`, { updates });
554
+ }
555
+ else {
556
+ info(`Task ${taskId} not found in taskList (total tasks: ${dashboard.taskList.length})`, {
557
+ existingTaskIds: dashboard.taskList.map(t => t.id),
558
+ });
559
+ }
560
+ });
561
+ return updated;
562
+ }
563
+ export async function removeTaskFromList(taskId) {
564
+ const dashboardPath = getDashboardPath();
565
+ await withFileLock(dashboardPath, async () => {
566
+ const content = await fs.readFile(dashboardPath, 'utf-8');
567
+ const parsed = JSON.parse(content);
568
+ // 既存データ互換性
569
+ const dashboard = {
570
+ ...parsed,
571
+ memberStatus: parsed.memberStatus ?? createDefaultMemberStatus(),
572
+ taskList: parsed.taskList ?? [],
573
+ };
574
+ dashboard.taskList = dashboard.taskList.filter(t => t.id !== taskId);
575
+ dashboard.lastUpdated = new Date().toISOString();
576
+ await fs.writeFile(dashboardPath, JSON.stringify(dashboard, null, 2), 'utf-8');
577
+ await writeDashboard(dashboard);
578
+ });
579
+ }
580
+ function formatDateForArchive(date) {
581
+ const year = date.getFullYear();
582
+ const month = String(date.getMonth() + 1).padStart(2, '0');
583
+ const day = String(date.getDate()).padStart(2, '0');
584
+ return `${year}-${month}-${day}`;
585
+ }
586
+ function getArchivePath(role, date) {
587
+ const dateStr = formatDateForArchive(date);
588
+ return path.join(getDevTeamPath(), 'archive', 'queue', role, `${dateStr}.json`);
589
+ }
590
+ export async function archiveReadMessages(role) {
591
+ const queuePath = getQueuePath(role);
592
+ const now = new Date();
593
+ const archivePath = getArchivePath(role, now);
594
+ let archivedCount = 0;
595
+ // Ensure archive directory exists
596
+ const archiveDir = path.dirname(archivePath);
597
+ await fs.mkdir(archiveDir, { recursive: true });
598
+ // Ensure archive file exists
599
+ await ensureFileExists(archivePath);
600
+ await withFileLock(queuePath, async () => {
601
+ // Read current queue
602
+ const queueContent = await fs.readFile(queuePath, 'utf-8');
603
+ const queue = JSON.parse(queueContent);
604
+ // Extract read messages
605
+ const readMessages = queue.messages.filter(m => m.read);
606
+ if (readMessages.length === 0) {
607
+ return;
608
+ }
609
+ // Lock archive file and merge
610
+ await withFileLock(archivePath, async () => {
611
+ let archive;
612
+ try {
613
+ const archiveContent = await fs.readFile(archivePath, 'utf-8');
614
+ const parsed = JSON.parse(archiveContent);
615
+ if (parsed.role && Array.isArray(parsed.messages)) {
616
+ archive = parsed;
617
+ }
618
+ else {
619
+ archive = {
620
+ role,
621
+ archivedAt: now.toISOString(),
622
+ messages: [],
623
+ };
624
+ }
625
+ }
626
+ catch {
627
+ archive = {
628
+ role,
629
+ archivedAt: now.toISOString(),
630
+ messages: [],
631
+ };
632
+ }
633
+ // Get existing message IDs for deduplication
634
+ const existingIds = new Set(archive.messages.map(m => m.id));
635
+ // Add new messages (skip duplicates)
636
+ for (const message of readMessages) {
637
+ if (!existingIds.has(message.id)) {
638
+ archive.messages.push(message);
639
+ archivedCount++;
640
+ }
641
+ }
642
+ // Update archivedAt timestamp
643
+ archive.archivedAt = now.toISOString();
644
+ // Write archive file
645
+ await fs.writeFile(archivePath, JSON.stringify(archive, null, 2), 'utf-8');
646
+ });
647
+ // Remove read messages from queue
648
+ queue.messages = queue.messages.filter(m => !m.read);
649
+ queue.lastUpdated = now.toISOString();
650
+ await fs.writeFile(queuePath, JSON.stringify(queue, null, 2), 'utf-8');
651
+ });
652
+ info(`Archived ${archivedCount} messages for ${role}`, { archivePath });
653
+ return {
654
+ archivedCount,
655
+ archivePath,
656
+ };
657
+ }
658
+ /**
659
+ * 親タスクIDから子タスク一覧を取得する
660
+ */
661
+ export async function getChildTasks(parentId) {
662
+ const dashboard = await getDashboard();
663
+ return dashboard.taskList.filter(task => task.parentTaskId === parentId);
664
+ }
665
+ /**
666
+ * 親子関係を設定する
667
+ * - 子タスクにparentTaskIdを設定
668
+ * - 親タスクのchildTaskIdsに子タスクIDを追加
669
+ */
670
+ export async function linkParentChild(parentId, childId) {
671
+ const dashboardPath = getDashboardPath();
672
+ await withFileLock(dashboardPath, async () => {
673
+ const content = await fs.readFile(dashboardPath, 'utf-8');
674
+ const parsed = JSON.parse(content);
675
+ const dashboard = {
676
+ ...parsed,
677
+ memberStatus: parsed.memberStatus ?? createDefaultMemberStatus(),
678
+ taskList: parsed.taskList ?? [],
679
+ };
680
+ // Find parent and child tasks
681
+ const parentIndex = dashboard.taskList.findIndex(t => t.id === parentId);
682
+ const childIndex = dashboard.taskList.findIndex(t => t.id === childId);
683
+ if (parentIndex === -1) {
684
+ throw new Error(`Parent task not found: ${parentId}`);
685
+ }
686
+ if (childIndex === -1) {
687
+ throw new Error(`Child task not found: ${childId}`);
688
+ }
689
+ // Set parentTaskId on child
690
+ dashboard.taskList[childIndex].parentTaskId = parentId;
691
+ // Add childId to parent's childTaskIds
692
+ const parent = dashboard.taskList[parentIndex];
693
+ if (!parent.childTaskIds) {
694
+ parent.childTaskIds = [];
695
+ }
696
+ if (!parent.childTaskIds.includes(childId)) {
697
+ parent.childTaskIds.push(childId);
698
+ }
699
+ dashboard.lastUpdated = new Date().toISOString();
700
+ await fs.writeFile(dashboardPath, JSON.stringify(dashboard, null, 2), 'utf-8');
701
+ await writeDashboard(dashboard);
702
+ info(`Linked parent-child relationship`, { parentId, childId });
703
+ });
704
+ }
705
+ //# sourceMappingURL=queue.js.map