kanbaii 0.1.2 → 0.2.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 (264) hide show
  1. package/dashboard/404.html +1 -1
  2. package/dashboard/_next/static/chunks/23-74779bf2fc2560e3.js +1 -0
  3. package/dashboard/_next/static/chunks/app/layout-c4919caf1b2a656c.js +1 -0
  4. package/dashboard/_next/static/chunks/app/page-0cbaeeff2545315a.js +1 -0
  5. package/dashboard/_next/static/css/7669ae094c660f9a.css +1 -0
  6. package/dashboard/index.html +1 -1
  7. package/dashboard/index.txt +3 -3
  8. package/dist/cli/banner.d.ts +0 -1
  9. package/dist/cli/banner.js +0 -1
  10. package/dist/cli/doctor.d.ts +0 -1
  11. package/dist/cli/doctor.js +0 -1
  12. package/dist/cli/index.d.ts +0 -1
  13. package/dist/cli/index.js +0 -1
  14. package/dist/server/engines/claudeRunner.d.ts +0 -1
  15. package/dist/server/engines/claudeRunner.js +9 -3
  16. package/dist/server/engines/coordinator.d.ts +0 -1
  17. package/dist/server/engines/coordinator.js +0 -1
  18. package/dist/server/engines/coordinatorPrompt.d.ts +0 -1
  19. package/dist/server/engines/coordinatorPrompt.js +2 -2
  20. package/dist/server/engines/dependencyResolver.d.ts +0 -1
  21. package/dist/server/engines/dependencyResolver.js +0 -1
  22. package/dist/server/engines/planner.d.ts +0 -1
  23. package/dist/server/engines/planner.js +0 -1
  24. package/dist/server/engines/plannerStore.d.ts +0 -1
  25. package/dist/server/engines/plannerStore.js +0 -1
  26. package/dist/server/engines/ralph.d.ts +0 -1
  27. package/dist/server/engines/ralph.js +0 -1
  28. package/dist/server/engines/runStore.d.ts +0 -1
  29. package/dist/server/engines/runStore.js +0 -1
  30. package/dist/server/engines/taskRouter.d.ts +0 -1
  31. package/dist/server/engines/taskRouter.js +3 -3
  32. package/dist/server/engines/teams.d.ts +0 -1
  33. package/dist/server/engines/teams.js +0 -1
  34. package/dist/server/engines/workerPool.d.ts +0 -1
  35. package/dist/server/engines/workerPool.js +16 -1
  36. package/dist/server/index.d.ts +0 -1
  37. package/dist/server/index.js +72 -8
  38. package/dist/server/lib/authMiddleware.d.ts +0 -1
  39. package/dist/server/lib/authMiddleware.js +0 -1
  40. package/dist/server/lib/fileWatcher.d.ts +0 -1
  41. package/dist/server/lib/fileWatcher.js +0 -1
  42. package/dist/server/lib/generateId.d.ts +0 -1
  43. package/dist/server/lib/generateId.js +0 -1
  44. package/dist/server/lib/promptSanitizer.d.ts +5 -0
  45. package/dist/server/lib/promptSanitizer.js +23 -0
  46. package/dist/server/lib/rateLimiter.d.ts +4 -0
  47. package/dist/server/lib/rateLimiter.js +33 -0
  48. package/dist/server/lib/requestLogger.d.ts +2 -0
  49. package/dist/server/lib/requestLogger.js +23 -0
  50. package/dist/server/lib/safePath.d.ts +5 -0
  51. package/dist/server/lib/safePath.js +24 -0
  52. package/dist/server/lib/schemas.d.ts +0 -1
  53. package/dist/server/lib/schemas.js +0 -1
  54. package/dist/server/lib/secretsEncryption.d.ts +3 -0
  55. package/dist/server/lib/secretsEncryption.js +38 -0
  56. package/dist/server/lib/typedEmit.d.ts +0 -1
  57. package/dist/server/lib/typedEmit.js +0 -1
  58. package/dist/server/routes/agents.d.ts +0 -1
  59. package/dist/server/routes/agents.js +0 -1
  60. package/dist/server/routes/auth.d.ts +0 -1
  61. package/dist/server/routes/auth.js +0 -1
  62. package/dist/server/routes/costs.d.ts +0 -1
  63. package/dist/server/routes/costs.js +0 -1
  64. package/dist/server/routes/escalation.d.ts +0 -1
  65. package/dist/server/routes/escalation.js +0 -1
  66. package/dist/server/routes/generate.d.ts +0 -1
  67. package/dist/server/routes/generate.js +1 -3
  68. package/dist/server/routes/mcp.d.ts +0 -1
  69. package/dist/server/routes/mcp.js +0 -1
  70. package/dist/server/routes/planner.d.ts +0 -1
  71. package/dist/server/routes/planner.js +0 -1
  72. package/dist/server/routes/plugins.d.ts +0 -1
  73. package/dist/server/routes/plugins.js +0 -1
  74. package/dist/server/routes/projects.d.ts +0 -1
  75. package/dist/server/routes/projects.js +0 -1
  76. package/dist/server/routes/ralph.d.ts +0 -1
  77. package/dist/server/routes/ralph.js +0 -1
  78. package/dist/server/routes/scheduler.d.ts +0 -1
  79. package/dist/server/routes/scheduler.js +0 -1
  80. package/dist/server/routes/settings.d.ts +0 -1
  81. package/dist/server/routes/settings.js +0 -1
  82. package/dist/server/routes/skills.d.ts +0 -1
  83. package/dist/server/routes/skills.js +0 -1
  84. package/dist/server/routes/soul.d.ts +0 -1
  85. package/dist/server/routes/soul.js +0 -1
  86. package/dist/server/routes/system.d.ts +0 -1
  87. package/dist/server/routes/system.js +15 -12
  88. package/dist/server/routes/tasks.d.ts +0 -1
  89. package/dist/server/routes/tasks.js +0 -1
  90. package/dist/server/routes/teams.d.ts +0 -1
  91. package/dist/server/routes/teams.js +0 -1
  92. package/dist/server/routes/terminal.d.ts +0 -1
  93. package/dist/server/routes/terminal.js +0 -1
  94. package/dist/server/routes/voice.d.ts +0 -1
  95. package/dist/server/routes/voice.js +0 -1
  96. package/dist/server/routes/workItems.d.ts +0 -1
  97. package/dist/server/routes/workItems.js +11 -1
  98. package/dist/server/services/agentRegistry.d.ts +0 -1
  99. package/dist/server/services/agentRegistry.js +0 -1
  100. package/dist/server/services/authService.d.ts +0 -1
  101. package/dist/server/services/authService.js +9 -2
  102. package/dist/server/services/claudeUsage.d.ts +0 -1
  103. package/dist/server/services/claudeUsage.js +32 -7
  104. package/dist/server/services/costTracker.d.ts +0 -1
  105. package/dist/server/services/costTracker.js +0 -1
  106. package/dist/server/services/escalationService.d.ts +0 -1
  107. package/dist/server/services/escalationService.js +5 -2
  108. package/dist/server/services/mcpConfig.d.ts +0 -1
  109. package/dist/server/services/mcpConfig.js +1 -2
  110. package/dist/server/services/pluginLoader.d.ts +0 -1
  111. package/dist/server/services/pluginLoader.js +19 -1
  112. package/dist/server/services/projectStore.d.ts +0 -1
  113. package/dist/server/services/projectStore.js +2 -2
  114. package/dist/server/services/schedulerService.d.ts +0 -1
  115. package/dist/server/services/schedulerService.js +0 -1
  116. package/dist/server/services/settingsService.d.ts +0 -1
  117. package/dist/server/services/settingsService.js +34 -3
  118. package/dist/server/services/skillsRegistry.d.ts +0 -1
  119. package/dist/server/services/skillsRegistry.js +0 -1
  120. package/dist/server/services/soulStore.d.ts +0 -1
  121. package/dist/server/services/soulStore.js +0 -1
  122. package/dist/server/services/telegramService.d.ts +0 -1
  123. package/dist/server/services/telegramService.js +0 -1
  124. package/dist/server/services/terminalManager.d.ts +0 -1
  125. package/dist/server/services/terminalManager.js +3 -2
  126. package/dist/server/services/workItemStore.d.ts +10 -4
  127. package/dist/server/services/workItemStore.js +57 -20
  128. package/dist/shared/types.d.ts +1 -1
  129. package/dist/shared/types.js +0 -1
  130. package/package.json +15 -4
  131. package/dashboard/_next/static/chunks/722-5d5f6d00bb07f427.js +0 -1
  132. package/dashboard/_next/static/chunks/app/layout-f76af5203e5cab0e.js +0 -1
  133. package/dashboard/_next/static/chunks/app/page-e5035f252913cddb.js +0 -1
  134. package/dashboard/_next/static/css/1a1fb46093561e14.css +0 -1
  135. package/dist/cli/banner.d.ts.map +0 -1
  136. package/dist/cli/banner.js.map +0 -1
  137. package/dist/cli/doctor.d.ts.map +0 -1
  138. package/dist/cli/doctor.js.map +0 -1
  139. package/dist/cli/index.d.ts.map +0 -1
  140. package/dist/cli/index.js.map +0 -1
  141. package/dist/server/__tests__/api.test.d.ts +0 -2
  142. package/dist/server/__tests__/api.test.d.ts.map +0 -1
  143. package/dist/server/__tests__/api.test.js +0 -279
  144. package/dist/server/__tests__/api.test.js.map +0 -1
  145. package/dist/server/__tests__/projectStore.test.d.ts +0 -2
  146. package/dist/server/__tests__/projectStore.test.d.ts.map +0 -1
  147. package/dist/server/__tests__/projectStore.test.js +0 -153
  148. package/dist/server/__tests__/projectStore.test.js.map +0 -1
  149. package/dist/server/__tests__/setup.d.ts +0 -2
  150. package/dist/server/__tests__/setup.d.ts.map +0 -1
  151. package/dist/server/__tests__/setup.js +0 -14
  152. package/dist/server/__tests__/setup.js.map +0 -1
  153. package/dist/server/__tests__/workItemStore.test.d.ts +0 -2
  154. package/dist/server/__tests__/workItemStore.test.d.ts.map +0 -1
  155. package/dist/server/__tests__/workItemStore.test.js +0 -323
  156. package/dist/server/__tests__/workItemStore.test.js.map +0 -1
  157. package/dist/server/engines/claudeRunner.d.ts.map +0 -1
  158. package/dist/server/engines/claudeRunner.js.map +0 -1
  159. package/dist/server/engines/coordinator.d.ts.map +0 -1
  160. package/dist/server/engines/coordinator.js.map +0 -1
  161. package/dist/server/engines/coordinatorPrompt.d.ts.map +0 -1
  162. package/dist/server/engines/coordinatorPrompt.js.map +0 -1
  163. package/dist/server/engines/dependencyResolver.d.ts.map +0 -1
  164. package/dist/server/engines/dependencyResolver.js.map +0 -1
  165. package/dist/server/engines/planner.d.ts.map +0 -1
  166. package/dist/server/engines/planner.js.map +0 -1
  167. package/dist/server/engines/plannerStore.d.ts.map +0 -1
  168. package/dist/server/engines/plannerStore.js.map +0 -1
  169. package/dist/server/engines/ralph.d.ts.map +0 -1
  170. package/dist/server/engines/ralph.js.map +0 -1
  171. package/dist/server/engines/runStore.d.ts.map +0 -1
  172. package/dist/server/engines/runStore.js.map +0 -1
  173. package/dist/server/engines/taskRouter.d.ts.map +0 -1
  174. package/dist/server/engines/taskRouter.js.map +0 -1
  175. package/dist/server/engines/teams.d.ts.map +0 -1
  176. package/dist/server/engines/teams.js.map +0 -1
  177. package/dist/server/engines/workerPool.d.ts.map +0 -1
  178. package/dist/server/engines/workerPool.js.map +0 -1
  179. package/dist/server/index.d.ts.map +0 -1
  180. package/dist/server/index.js.map +0 -1
  181. package/dist/server/lib/authMiddleware.d.ts.map +0 -1
  182. package/dist/server/lib/authMiddleware.js.map +0 -1
  183. package/dist/server/lib/fileWatcher.d.ts.map +0 -1
  184. package/dist/server/lib/fileWatcher.js.map +0 -1
  185. package/dist/server/lib/generateId.d.ts.map +0 -1
  186. package/dist/server/lib/generateId.js.map +0 -1
  187. package/dist/server/lib/schemas.d.ts.map +0 -1
  188. package/dist/server/lib/schemas.js.map +0 -1
  189. package/dist/server/lib/typedEmit.d.ts.map +0 -1
  190. package/dist/server/lib/typedEmit.js.map +0 -1
  191. package/dist/server/routes/agents.d.ts.map +0 -1
  192. package/dist/server/routes/agents.js.map +0 -1
  193. package/dist/server/routes/auth.d.ts.map +0 -1
  194. package/dist/server/routes/auth.js.map +0 -1
  195. package/dist/server/routes/costs.d.ts.map +0 -1
  196. package/dist/server/routes/costs.js.map +0 -1
  197. package/dist/server/routes/escalation.d.ts.map +0 -1
  198. package/dist/server/routes/escalation.js.map +0 -1
  199. package/dist/server/routes/generate.d.ts.map +0 -1
  200. package/dist/server/routes/generate.js.map +0 -1
  201. package/dist/server/routes/mcp.d.ts.map +0 -1
  202. package/dist/server/routes/mcp.js.map +0 -1
  203. package/dist/server/routes/planner.d.ts.map +0 -1
  204. package/dist/server/routes/planner.js.map +0 -1
  205. package/dist/server/routes/plugins.d.ts.map +0 -1
  206. package/dist/server/routes/plugins.js.map +0 -1
  207. package/dist/server/routes/projects.d.ts.map +0 -1
  208. package/dist/server/routes/projects.js.map +0 -1
  209. package/dist/server/routes/ralph.d.ts.map +0 -1
  210. package/dist/server/routes/ralph.js.map +0 -1
  211. package/dist/server/routes/scheduler.d.ts.map +0 -1
  212. package/dist/server/routes/scheduler.js.map +0 -1
  213. package/dist/server/routes/settings.d.ts.map +0 -1
  214. package/dist/server/routes/settings.js.map +0 -1
  215. package/dist/server/routes/skills.d.ts.map +0 -1
  216. package/dist/server/routes/skills.js.map +0 -1
  217. package/dist/server/routes/soul.d.ts.map +0 -1
  218. package/dist/server/routes/soul.js.map +0 -1
  219. package/dist/server/routes/system.d.ts.map +0 -1
  220. package/dist/server/routes/system.js.map +0 -1
  221. package/dist/server/routes/tasks.d.ts.map +0 -1
  222. package/dist/server/routes/tasks.js.map +0 -1
  223. package/dist/server/routes/teams.d.ts.map +0 -1
  224. package/dist/server/routes/teams.js.map +0 -1
  225. package/dist/server/routes/terminal.d.ts.map +0 -1
  226. package/dist/server/routes/terminal.js.map +0 -1
  227. package/dist/server/routes/voice.d.ts.map +0 -1
  228. package/dist/server/routes/voice.js.map +0 -1
  229. package/dist/server/routes/workItems.d.ts.map +0 -1
  230. package/dist/server/routes/workItems.js.map +0 -1
  231. package/dist/server/services/agentRegistry.d.ts.map +0 -1
  232. package/dist/server/services/agentRegistry.js.map +0 -1
  233. package/dist/server/services/authService.d.ts.map +0 -1
  234. package/dist/server/services/authService.js.map +0 -1
  235. package/dist/server/services/claudeUsage.d.ts.map +0 -1
  236. package/dist/server/services/claudeUsage.js.map +0 -1
  237. package/dist/server/services/costTracker.d.ts.map +0 -1
  238. package/dist/server/services/costTracker.js.map +0 -1
  239. package/dist/server/services/escalationService.d.ts.map +0 -1
  240. package/dist/server/services/escalationService.js.map +0 -1
  241. package/dist/server/services/mcpConfig.d.ts.map +0 -1
  242. package/dist/server/services/mcpConfig.js.map +0 -1
  243. package/dist/server/services/pluginLoader.d.ts.map +0 -1
  244. package/dist/server/services/pluginLoader.js.map +0 -1
  245. package/dist/server/services/projectStore.d.ts.map +0 -1
  246. package/dist/server/services/projectStore.js.map +0 -1
  247. package/dist/server/services/schedulerService.d.ts.map +0 -1
  248. package/dist/server/services/schedulerService.js.map +0 -1
  249. package/dist/server/services/settingsService.d.ts.map +0 -1
  250. package/dist/server/services/settingsService.js.map +0 -1
  251. package/dist/server/services/skillsRegistry.d.ts.map +0 -1
  252. package/dist/server/services/skillsRegistry.js.map +0 -1
  253. package/dist/server/services/soulStore.d.ts.map +0 -1
  254. package/dist/server/services/soulStore.js.map +0 -1
  255. package/dist/server/services/telegramService.d.ts.map +0 -1
  256. package/dist/server/services/telegramService.js.map +0 -1
  257. package/dist/server/services/terminalManager.d.ts.map +0 -1
  258. package/dist/server/services/terminalManager.js.map +0 -1
  259. package/dist/server/services/workItemStore.d.ts.map +0 -1
  260. package/dist/server/services/workItemStore.js.map +0 -1
  261. package/dist/shared/types.d.ts.map +0 -1
  262. package/dist/shared/types.js.map +0 -1
  263. /package/dashboard/_next/static/{WZgWWvl5DWIMdBOLDHyIK → U_eLKLn69LJkGH6AtpAhH}/_buildManifest.js +0 -0
  264. /package/dashboard/_next/static/{WZgWWvl5DWIMdBOLDHyIK → U_eLKLn69LJkGH6AtpAhH}/_ssgManifest.js +0 -0
@@ -111,4 +111,3 @@ router.delete('/:taskId', (req, res) => {
111
111
  }
112
112
  });
113
113
  exports.default = router;
114
- //# sourceMappingURL=tasks.js.map
@@ -1,3 +1,2 @@
1
1
  declare const router: import("express-serve-static-core").Router;
2
2
  export default router;
3
- //# sourceMappingURL=teams.d.ts.map
@@ -88,4 +88,3 @@ router.get('/workers', (_req, res) => {
88
88
  res.json({ ok: true, data: (0, workerPool_1.getPoolStatus)() });
89
89
  });
90
90
  exports.default = router;
91
- //# sourceMappingURL=teams.js.map
@@ -1,3 +1,2 @@
1
1
  declare const router: import("express-serve-static-core").Router;
2
2
  export default router;
3
- //# sourceMappingURL=terminal.d.ts.map
@@ -82,4 +82,3 @@ router.post('/reset', (req, res) => {
82
82
  res.json({ ok: true });
83
83
  });
84
84
  exports.default = router;
85
- //# sourceMappingURL=terminal.js.map
@@ -1,3 +1,2 @@
1
1
  declare const router: import("express-serve-static-core").Router;
2
2
  export default router;
3
- //# sourceMappingURL=voice.d.ts.map
@@ -64,4 +64,3 @@ router.post('/transcribe', express_2.default.raw({ type: '*/*', limit: '10mb' })
64
64
  apiReq.end();
65
65
  });
66
66
  exports.default = router;
67
- //# sourceMappingURL=voice.js.map
@@ -1,3 +1,2 @@
1
1
  declare const router: import("express-serve-static-core").Router;
2
2
  export default router;
3
- //# sourceMappingURL=workItems.d.ts.map
@@ -82,6 +82,17 @@ router.patch('/:wiId', (req, res) => {
82
82
  throw err;
83
83
  }
84
84
  });
85
+ // POST /api/projects/:slug/work-items/:wiId/reorder
86
+ router.post('/:wiId/reorder', (req, res) => {
87
+ const { order } = req.body;
88
+ if (typeof order !== 'number')
89
+ return res.status(400).json({ ok: false, error: 'order (number) required' });
90
+ const item = workItemStore.reorderWorkItem(req.params.slug, req.params.wiId, order);
91
+ if (!item)
92
+ return res.status(404).json({ ok: false, error: 'Work item not found' });
93
+ (0, typedEmit_1.emit)('workItem:updated', { projectSlug: req.params.slug, workItem: item });
94
+ res.json({ ok: true, data: item });
95
+ });
85
96
  // DELETE /api/projects/:slug/work-items/:wiId
86
97
  router.delete('/:wiId', (req, res) => {
87
98
  try {
@@ -97,4 +108,3 @@ router.delete('/:wiId', (req, res) => {
97
108
  }
98
109
  });
99
110
  exports.default = router;
100
- //# sourceMappingURL=workItems.js.map
@@ -18,4 +18,3 @@ export declare function suggestAgent(taskTags: string[]): {
18
18
  score: number;
19
19
  matchCount: number;
20
20
  } | null;
21
- //# sourceMappingURL=agentRegistry.d.ts.map
@@ -147,4 +147,3 @@ function suggestAgent(taskTags) {
147
147
  }
148
148
  return best;
149
149
  }
150
- //# sourceMappingURL=agentRegistry.js.map
@@ -22,4 +22,3 @@ export declare function login(username: string, password: string): {
22
22
  };
23
23
  export declare function verifyToken(token: string): TokenPayload | null;
24
24
  export declare function hasUsers(): boolean;
25
- //# sourceMappingURL=authService.d.ts.map
@@ -24,7 +24,12 @@ function generateSalt() {
24
24
  // ─── JWT-like tokens (simple HMAC-based, no jsonwebtoken dependency) ───
25
25
  function getSecret() {
26
26
  const auth = (0, settingsService_1.getSection)('auth');
27
- return auth.secret || 'kanbaii-default-secret-change-me';
27
+ if (!auth.secret || auth.secret === 'kanbaii-default-secret-change-me') {
28
+ const generated = crypto_1.default.randomBytes(32).toString('hex');
29
+ (0, settingsService_1.updateSection)('auth', { secret: generated });
30
+ return generated;
31
+ }
32
+ return auth.secret;
28
33
  }
29
34
  function base64url(str) {
30
35
  return Buffer.from(str).toString('base64url');
@@ -73,6 +78,9 @@ function isAuthEnabled() {
73
78
  return (0, settingsService_1.getSection)('auth').enabled;
74
79
  }
75
80
  function register(username, password) {
81
+ if (password.length < 8) {
82
+ throw new Error('Password must be at least 8 characters');
83
+ }
76
84
  const users = readUsers();
77
85
  if (users.find(u => u.username === username)) {
78
86
  throw new Error('Username already exists');
@@ -121,4 +129,3 @@ function parseExpiry(str) {
121
129
  default: return 86400;
122
130
  }
123
131
  }
124
- //# sourceMappingURL=authService.js.map
@@ -12,4 +12,3 @@ export declare function getCachedUsage(): ClaudeUsageData | null;
12
12
  export declare function fetchClaudeUsage(): Promise<void>;
13
13
  export declare function startPolling(intervalMs?: number): void;
14
14
  export declare function stopPolling(): void;
15
- //# sourceMappingURL=claudeUsage.d.ts.map
@@ -15,6 +15,9 @@ const typedEmit_1 = require("../lib/typedEmit");
15
15
  // ─── Cache ───
16
16
  let usageCache = null;
17
17
  let pollInterval = null;
18
+ // ─── Backoff ───
19
+ let _backoffMs = 0;
20
+ const MAX_BACKOFF = 5 * 60 * 1000; // 5 minutes max
18
21
  function getCachedUsage() {
19
22
  return usageCache;
20
23
  }
@@ -77,7 +80,8 @@ function fetchClaudeUsage() {
77
80
  res.on('data', (c) => (data += c));
78
81
  res.on('end', () => {
79
82
  if (res.statusCode === 429) {
80
- console.warn('[claude-usage] Rate limited (429), waiting for next poll');
83
+ _backoffMs = Math.min((_backoffMs || 15000) * 2, MAX_BACKOFF);
84
+ console.warn(`[claude-usage] Rate limited, backing off ${Math.round(_backoffMs / 1000)}s`);
81
85
  resolve();
82
86
  return;
83
87
  }
@@ -122,6 +126,7 @@ function fetchClaudeUsage() {
122
126
  });
123
127
  }
124
128
  usageCache = { entries, timestamp: new Date().toISOString() };
129
+ _backoffMs = 0;
125
130
  // Broadcast via Socket.IO
126
131
  try {
127
132
  (0, typedEmit_1.getIO)().emit('claude-usage', usageCache);
@@ -132,23 +137,43 @@ function fetchClaudeUsage() {
132
137
  resolve();
133
138
  });
134
139
  });
135
- req.on('error', (err) => { console.warn(`[claude-usage] Request error: ${err.message}`); resolve(); });
140
+ req.on('error', (err) => {
141
+ _backoffMs = Math.min((_backoffMs || 15000) * 2, MAX_BACKOFF);
142
+ console.warn(`[claude-usage] Request error: ${err.message}, backing off ${Math.round(_backoffMs / 1000)}s`);
143
+ resolve();
144
+ });
136
145
  req.setTimeout(15000, () => { console.warn('[claude-usage] Request timeout (15s)'); req.destroy(); resolve(); });
137
146
  req.end();
138
147
  });
139
148
  }
140
149
  // ─── Polling ───
150
+ let _pollActive = false;
151
+ let _pollTimer = null;
141
152
  function startPolling(intervalMs = 60000) {
142
- if (pollInterval)
153
+ if (_pollActive)
143
154
  return;
144
- fetchClaudeUsage(); // Immediate first fetch
145
- pollInterval = setInterval(() => fetchClaudeUsage(), intervalMs);
146
- console.log(`[claude-usage] Polling started (${intervalMs / 1000}s interval)`);
155
+ _pollActive = true;
156
+ console.log(`[claude-usage] Polling started (${intervalMs / 1000}s base interval)`);
157
+ const poll = async () => {
158
+ if (!_pollActive)
159
+ return;
160
+ await fetchClaudeUsage();
161
+ if (!_pollActive)
162
+ return;
163
+ const delay = _backoffMs > 0 ? _backoffMs : intervalMs;
164
+ _pollTimer = setTimeout(poll, delay);
165
+ };
166
+ poll(); // Immediate first fetch
147
167
  }
148
168
  function stopPolling() {
169
+ _pollActive = false;
170
+ if (_pollTimer) {
171
+ clearTimeout(_pollTimer);
172
+ _pollTimer = null;
173
+ }
174
+ // Legacy: clear interval reference if any
149
175
  if (pollInterval) {
150
176
  clearInterval(pollInterval);
151
177
  pollInterval = null;
152
178
  }
153
179
  }
154
- //# sourceMappingURL=claudeUsage.js.map
@@ -41,4 +41,3 @@ export declare function listExecutions(opts?: {
41
41
  }): ExecutionRecord[];
42
42
  export declare function getSummary(projectSlug?: string): CostSummary;
43
43
  export declare function clearExecutions(projectSlug?: string): void;
44
- //# sourceMappingURL=costTracker.d.ts.map
@@ -116,4 +116,3 @@ function clearExecutions(projectSlug) {
116
116
  writeUsage({ executions: [] });
117
117
  }
118
118
  }
119
- //# sourceMappingURL=costTracker.js.map
@@ -28,4 +28,3 @@ export declare function getEscalationStatus(): {
28
28
  responded: boolean;
29
29
  };
30
30
  export declare function clearEscalation(): void;
31
- //# sourceMappingURL=escalationService.d.ts.map
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.setSourceOverride = setSourceOverride;
4
7
  exports.createEscalation = createEscalation;
@@ -6,6 +9,7 @@ exports.respondToEscalation = respondToEscalation;
6
9
  exports.getPendingEscalation = getPendingEscalation;
7
10
  exports.getEscalationStatus = getEscalationStatus;
8
11
  exports.clearEscalation = clearEscalation;
12
+ const crypto_1 = __importDefault(require("crypto"));
9
13
  const typedEmit_1 = require("../lib/typedEmit");
10
14
  const telegramService_1 = require("./telegramService");
11
15
  const settingsService_1 = require("./settingsService");
@@ -18,7 +22,7 @@ function setSourceOverride(source) { sourceOverride = source; }
18
22
  // ─── API ───
19
23
  function createEscalation(data) {
20
24
  const escalation = {
21
- id: `esc-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
25
+ id: `esc-${crypto_1.default.randomBytes(12).toString('hex')}`,
22
26
  source: sourceOverride || data.source,
23
27
  taskId: data.taskId,
24
28
  taskTitle: data.taskTitle,
@@ -118,4 +122,3 @@ function clearEscalation() {
118
122
  }
119
123
  timeoutHandles.clear();
120
124
  }
121
- //# sourceMappingURL=escalationService.js.map
@@ -28,4 +28,3 @@ export declare function testServer(name: string): Promise<{
28
28
  * @param onlyKanbaii If true, only include the KANBAII escalation server (fast). Default: true.
29
29
  */
30
30
  export declare function generateMcpConfigForClaude(onlyKanbaii?: boolean): string | null;
31
- //# sourceMappingURL=mcpConfig.d.ts.map
@@ -122,7 +122,7 @@ async function testServer(name) {
122
122
  }, 3000);
123
123
  const proc = spawn(server.command, server.args || [], {
124
124
  env: { ...process.env, ...server.env },
125
- shell: true,
125
+ windowsHide: true,
126
126
  stdio: ['pipe', 'pipe', 'pipe'],
127
127
  });
128
128
  proc.on('error', (err) => {
@@ -171,4 +171,3 @@ function generateMcpConfigForClaude(onlyKanbaii = true) {
171
171
  fs_1.default.writeFileSync(tmpFile, JSON.stringify({ mcpServers: mcpConfig }, null, 2), 'utf-8');
172
172
  return tmpFile;
173
173
  }
174
- //# sourceMappingURL=mcpConfig.js.map
@@ -36,4 +36,3 @@ export interface PluginEntry {
36
36
  export declare function scanPlugins(): PluginEntry[];
37
37
  export declare function togglePlugin(name: string, enabled: boolean): void;
38
38
  export declare function runHook(hookName: keyof PluginHooks, ctx: any): Promise<void>;
39
- //# sourceMappingURL=pluginLoader.d.ts.map
@@ -41,6 +41,25 @@ function scanPlugins() {
41
41
  for (const file of files) {
42
42
  try {
43
43
  const fullPath = path_1.default.join(PLUGINS_DIR, file);
44
+ // Validate plugin is within plugins directory
45
+ const resolvedPath = path_1.default.resolve(fullPath);
46
+ if (!resolvedPath.startsWith(path_1.default.resolve(PLUGINS_DIR))) {
47
+ console.warn(`[plugins] Rejected plugin outside plugins dir: ${file}`);
48
+ continue;
49
+ }
50
+ // Check for dangerous patterns
51
+ const content = fs_1.default.readFileSync(fullPath, 'utf-8');
52
+ const dangerousPatterns = [
53
+ /child_process/,
54
+ /require\s*\(\s*['"]fs['"]\s*\)/,
55
+ /process\.exit/,
56
+ /eval\s*\(/,
57
+ /Function\s*\(/,
58
+ ];
59
+ const hasDangerous = dangerousPatterns.some(p => p.test(content));
60
+ if (hasDangerous) {
61
+ console.warn(`[plugins] Plugin ${file} uses restricted APIs — loading with warning`);
62
+ }
44
63
  // Clear require cache for hot-reload
45
64
  delete require.cache[require.resolve(fullPath)];
46
65
  const mod = require(fullPath);
@@ -90,4 +109,3 @@ async function runHook(hookName, ctx) {
90
109
  }
91
110
  // Initial scan
92
111
  scanPlugins();
93
- //# sourceMappingURL=pluginLoader.js.map
@@ -4,4 +4,3 @@ export declare function getProject(slug: string): Project | null;
4
4
  export declare function createProject(input: unknown): Project;
5
5
  export declare function updateProject(slug: string, input: unknown): Project;
6
6
  export declare function deleteProject(slug: string): void;
7
- //# sourceMappingURL=projectStore.d.ts.map
@@ -12,6 +12,7 @@ const fs_1 = __importDefault(require("fs"));
12
12
  const path_1 = __importDefault(require("path"));
13
13
  const schemas_1 = require("../lib/schemas");
14
14
  const generateId_1 = require("../lib/generateId");
15
+ const safePath_1 = require("../lib/safePath");
15
16
  const DATA_DIR = path_1.default.resolve(process.env.KANBAII_DATA_DIR || path_1.default.join(process.cwd(), 'data', 'projects'));
16
17
  function ensureDir(dir) {
17
18
  if (!fs_1.default.existsSync(dir)) {
@@ -19,7 +20,7 @@ function ensureDir(dir) {
19
20
  }
20
21
  }
21
22
  function projectDir(slug) {
22
- return path_1.default.join(DATA_DIR, slug);
23
+ return (0, safePath_1.safePath)(DATA_DIR, slug);
23
24
  }
24
25
  function projectFile(slug) {
25
26
  return path_1.default.join(projectDir(slug), 'project.json');
@@ -120,4 +121,3 @@ function deleteProject(slug) {
120
121
  };
121
122
  writeProject(updated);
122
123
  }
123
- //# sourceMappingURL=projectStore.js.map
@@ -50,4 +50,3 @@ export declare function getSchedulerStatus(): {
50
50
  runningNow: number;
51
51
  nextDue: string | null;
52
52
  };
53
- //# sourceMappingURL=schedulerService.d.ts.map
@@ -275,4 +275,3 @@ function getSchedulerStatus() {
275
275
  nextDue: next?.nextRun || null,
276
276
  };
277
277
  }
278
- //# sourceMappingURL=schedulerService.js.map
@@ -41,4 +41,3 @@ export declare function getSettings(): AppSettings;
41
41
  export declare function updateSettings(partial: Partial<AppSettings>): AppSettings;
42
42
  export declare function getSection<K extends keyof AppSettings>(key: K): AppSettings[K];
43
43
  export declare function updateSection<K extends keyof AppSettings>(key: K, value: Partial<AppSettings[K]>): AppSettings;
44
- //# sourceMappingURL=settingsService.d.ts.map
@@ -9,6 +9,7 @@ exports.getSection = getSection;
9
9
  exports.updateSection = updateSection;
10
10
  const fs_1 = __importDefault(require("fs"));
11
11
  const path_1 = __importDefault(require("path"));
12
+ const secretsEncryption_1 = require("../lib/secretsEncryption");
12
13
  const DATA_DIR = path_1.default.resolve(process.env.KANBAII_DATA_DIR || path_1.default.join(process.cwd(), 'data', 'projects'));
13
14
  const SETTINGS_FILE = path_1.default.join(DATA_DIR, '..', '.settings.json');
14
15
  const DEFAULTS = {
@@ -59,7 +60,27 @@ function getSettings() {
59
60
  try {
60
61
  if (fs_1.default.existsSync(SETTINGS_FILE)) {
61
62
  const saved = JSON.parse(fs_1.default.readFileSync(SETTINGS_FILE, 'utf-8'));
62
- return deepMerge(DEFAULTS, saved);
63
+ const settings = deepMerge(DEFAULTS, saved);
64
+ // Auto-decrypt sensitive fields
65
+ if (settings.integrations.telegram.botToken) {
66
+ try {
67
+ settings.integrations.telegram.botToken = (0, secretsEncryption_1.decrypt)(settings.integrations.telegram.botToken);
68
+ }
69
+ catch { }
70
+ }
71
+ if (settings.integrations.voice.openaiApiKey) {
72
+ try {
73
+ settings.integrations.voice.openaiApiKey = (0, secretsEncryption_1.decrypt)(settings.integrations.voice.openaiApiKey);
74
+ }
75
+ catch { }
76
+ }
77
+ if (settings.auth.secret && (0, secretsEncryption_1.isEncrypted)(settings.auth.secret)) {
78
+ try {
79
+ settings.auth.secret = (0, secretsEncryption_1.decrypt)(settings.auth.secret);
80
+ }
81
+ catch { }
82
+ }
83
+ return settings;
63
84
  }
64
85
  }
65
86
  catch { }
@@ -71,7 +92,18 @@ function updateSettings(partial) {
71
92
  const dir = path_1.default.dirname(SETTINGS_FILE);
72
93
  if (!fs_1.default.existsSync(dir))
73
94
  fs_1.default.mkdirSync(dir, { recursive: true });
74
- fs_1.default.writeFileSync(SETTINGS_FILE, JSON.stringify(merged, null, 2), 'utf-8');
95
+ // Auto-encrypt sensitive fields before saving
96
+ const toSave = JSON.parse(JSON.stringify(merged)); // deep clone
97
+ if (toSave.integrations?.telegram?.botToken && !(0, secretsEncryption_1.isEncrypted)(toSave.integrations.telegram.botToken)) {
98
+ toSave.integrations.telegram.botToken = (0, secretsEncryption_1.encrypt)(toSave.integrations.telegram.botToken);
99
+ }
100
+ if (toSave.integrations?.voice?.openaiApiKey && !(0, secretsEncryption_1.isEncrypted)(toSave.integrations.voice.openaiApiKey)) {
101
+ toSave.integrations.voice.openaiApiKey = (0, secretsEncryption_1.encrypt)(toSave.integrations.voice.openaiApiKey);
102
+ }
103
+ if (toSave.auth?.secret && !(0, secretsEncryption_1.isEncrypted)(toSave.auth.secret)) {
104
+ toSave.auth.secret = (0, secretsEncryption_1.encrypt)(toSave.auth.secret);
105
+ }
106
+ fs_1.default.writeFileSync(SETTINGS_FILE, JSON.stringify(toSave, null, 2), 'utf-8');
75
107
  return merged;
76
108
  }
77
109
  function getSection(key) {
@@ -82,4 +114,3 @@ function updateSection(key, value) {
82
114
  current[key] = deepMerge(current[key], value);
83
115
  return updateSettings(current);
84
116
  }
85
- //# sourceMappingURL=settingsService.js.map
@@ -15,4 +15,3 @@ export declare function toggleSkill(name: string, enabled: boolean): void;
15
15
  * Build a system prompt from all enabled skills for Claude CLI --append-system-prompt
16
16
  */
17
17
  export declare function buildSkillsPrompt(): string | null;
18
- //# sourceMappingURL=skillsRegistry.d.ts.map
@@ -74,4 +74,3 @@ function buildSkillsPrompt() {
74
74
  const sections = enabled.map((s) => `## Skill: ${s.name}\n${s.description}\n\n${s.promptTemplate}`);
75
75
  return `# Active Skills\n\n${sections.join('\n\n---\n\n')}`;
76
76
  }
77
- //# sourceMappingURL=skillsRegistry.js.map
@@ -43,4 +43,3 @@ export declare function getConfig(projectSlug: string): SoulConfig;
43
43
  export declare function updateConfig(projectSlug: string, config: Partial<SoulConfig>): SoulConfig;
44
44
  export declare function getHealth(projectSlug: string): HealthMetrics;
45
45
  export declare function updateHealth(projectSlug: string, metrics: Partial<HealthMetrics>): HealthMetrics;
46
- //# sourceMappingURL=soulStore.d.ts.map
@@ -215,4 +215,3 @@ function updateHealth(projectSlug, metrics) {
215
215
  updateDocument(projectSlug, 'HEALTH.md', healthMd);
216
216
  return updated;
217
217
  }
218
- //# sourceMappingURL=soulStore.js.map
@@ -13,4 +13,3 @@ export declare function notifyRalphStarted(projectSlug: string, taskCount: numbe
13
13
  export declare function notifyRalphCompleted(projectSlug: string, completed: number, failed: number): void;
14
14
  export declare function notifyTeamsStarted(projectSlug: string, wiCount: number): void;
15
15
  export declare function notifyError(projectSlug: string, message: string): void;
16
- //# sourceMappingURL=telegramService.d.ts.map
@@ -141,4 +141,3 @@ function notifyTeamsStarted(projectSlug, wiCount) {
141
141
  function notifyError(projectSlug, message) {
142
142
  sendMessage(`🔴 *Error* on \`${projectSlug}\`\n${message}`).catch(() => { });
143
143
  }
144
- //# sourceMappingURL=telegramService.js.map
@@ -21,4 +21,3 @@ export declare function sendInput(projectSlug: string, data: string): void;
21
21
  export declare function resizeTerminal(projectSlug: string, cols: number, rows: number): void;
22
22
  export declare function killSession(projectSlug: string): void;
23
23
  export declare function resetSession(projectSlug: string): void;
24
- //# sourceMappingURL=terminalManager.d.ts.map
@@ -34,7 +34,9 @@ function spawnPty(projectSlug, workingDir, opts) {
34
34
  throw new Error('node-pty not available. Install: npm install node-pty');
35
35
  const isWindows = process.platform === 'win32';
36
36
  const shell = isWindows ? 'cmd.exe' : '/bin/bash';
37
- const claudeCmd = `claude${opts?.model ? ` --model ${opts.model}` : ''}`;
37
+ const validModels = ['opus', 'sonnet', 'haiku'];
38
+ const model = opts?.model && validModels.includes(opts.model) ? opts.model : undefined;
39
+ const claudeCmd = `claude${model ? ` --model ${model}` : ''}`;
38
40
  const shellArgs = isWindows ? ['/c', claudeCmd] : ['-c', claudeCmd];
39
41
  const cleanEnv = { ...process.env };
40
42
  delete cleanEnv.CLAUDECODE;
@@ -82,4 +84,3 @@ function resetSession(projectSlug) {
82
84
  killSession(projectSlug);
83
85
  sessions.delete(projectSlug);
84
86
  }
85
- //# sourceMappingURL=terminalManager.js.map
@@ -20,15 +20,21 @@ export declare function deleteTask(projectSlug: string, wiIdOrSlug: string, task
20
20
  */
21
21
  export declare function activateWorkItemIfNeeded(projectSlug: string, wiIdOrSlug: string): WorkItem | null;
22
22
  /**
23
- * Check if all tasks in a work item are in 'review' or 'done' columns
24
- * (nothing left in backlog, todo, in-progress). If so, move status to 'review'.
25
- * Only promotes from 'active' 'review', never from 'review' or 'done'.
23
+ * Auto-sync work item status based on task distribution:
24
+ * - active review: when all tasks are in review or done (nothing in backlog/todo/in-progress)
25
+ * - review done: when ALL tasks are in done column
26
+ * - review → active: if tasks move back to pending columns
26
27
  */
28
+ export declare function syncWorkItemStatus(projectSlug: string, wiIdOrSlug: string): WorkItem | null;
29
+ /** @deprecated Use syncWorkItemStatus instead */
27
30
  export declare function promoteWorkItemIfComplete(projectSlug: string, wiIdOrSlug: string): WorkItem | null;
31
+ /**
32
+ * Reorder a work item within its status column.
33
+ */
34
+ export declare function reorderWorkItem(projectSlug: string, wiIdOrSlug: string, newOrder: number): WorkItem | null;
28
35
  export declare function getTaskCounts(wi: WorkItem): Record<TaskColumnName, number>;
29
36
  export declare function getProgress(wi: WorkItem): {
30
37
  completed: number;
31
38
  total: number;
32
39
  percent: number;
33
40
  };
34
- //# sourceMappingURL=workItemStore.d.ts.map
@@ -13,7 +13,9 @@ exports.updateTask = updateTask;
13
13
  exports.moveTask = moveTask;
14
14
  exports.deleteTask = deleteTask;
15
15
  exports.activateWorkItemIfNeeded = activateWorkItemIfNeeded;
16
+ exports.syncWorkItemStatus = syncWorkItemStatus;
16
17
  exports.promoteWorkItemIfComplete = promoteWorkItemIfComplete;
18
+ exports.reorderWorkItem = reorderWorkItem;
17
19
  exports.getTaskCounts = getTaskCounts;
18
20
  exports.getProgress = getProgress;
19
21
  const fs_1 = __importDefault(require("fs"));
@@ -22,12 +24,13 @@ const types_1 = require("../../shared/types");
22
24
  const schemas_1 = require("../lib/schemas");
23
25
  const generateId_1 = require("../lib/generateId");
24
26
  const types_2 = require("../../shared/types");
27
+ const safePath_1 = require("../lib/safePath");
25
28
  const DATA_DIR = path_1.default.resolve(process.env.KANBAII_DATA_DIR || path_1.default.join(process.cwd(), 'data', 'projects'));
26
29
  function workItemsDir(projectSlug) {
27
- return path_1.default.join(DATA_DIR, projectSlug, 'work-items');
30
+ return (0, safePath_1.safePath)(DATA_DIR, projectSlug, 'work-items');
28
31
  }
29
- function workItemFile(projectSlug, wiSlug) {
30
- return path_1.default.join(workItemsDir(projectSlug), `${wiSlug}.json`);
32
+ function workItemFile(projectSlug, slug) {
33
+ return (0, safePath_1.safePath)(DATA_DIR, projectSlug, 'work-items', `${slug}.json`);
31
34
  }
32
35
  function ensureDir(dir) {
33
36
  if (!fs_1.default.existsSync(dir)) {
@@ -95,7 +98,7 @@ function listWorkItems(projectSlug) {
95
98
  if (wi)
96
99
  items.push(wi);
97
100
  }
98
- return items.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
101
+ return items.sort((a, b) => (a.order ?? 999) - (b.order ?? 999) || b.updatedAt.localeCompare(a.updatedAt));
99
102
  }
100
103
  function getWorkItem(projectSlug, idOrSlug) {
101
104
  return findWorkItemByIdOrSlug(projectSlug, idOrSlug);
@@ -245,7 +248,10 @@ function moveTask(projectSlug, wiIdOrSlug, taskId, input) {
245
248
  targetCol.splice(insertIdx, 0, task);
246
249
  wi.updatedAt = now;
247
250
  writeWorkItem(projectSlug, wi);
248
- return wi;
251
+ // Auto-sync work item status based on task distribution
252
+ syncWorkItemStatus(projectSlug, wiIdOrSlug);
253
+ // Re-read after potential status change
254
+ return findWorkItemByIdOrSlug(projectSlug, wiIdOrSlug) || wi;
249
255
  }
250
256
  function deleteTask(projectSlug, wiIdOrSlug, taskId) {
251
257
  const wi = findWorkItemByIdOrSlug(projectSlug, wiIdOrSlug);
@@ -276,30 +282,62 @@ function activateWorkItemIfNeeded(projectSlug, wiIdOrSlug) {
276
282
  return wi;
277
283
  }
278
284
  /**
279
- * Check if all tasks in a work item are in 'review' or 'done' columns
280
- * (nothing left in backlog, todo, in-progress). If so, move status to 'review'.
281
- * Only promotes from 'active' 'review', never from 'review' or 'done'.
285
+ * Auto-sync work item status based on task distribution:
286
+ * - active review: when all tasks are in review or done (nothing in backlog/todo/in-progress)
287
+ * - review done: when ALL tasks are in done column
288
+ * - review → active: if tasks move back to pending columns
282
289
  */
283
- function promoteWorkItemIfComplete(projectSlug, wiIdOrSlug) {
290
+ function syncWorkItemStatus(projectSlug, wiIdOrSlug) {
284
291
  const wi = findWorkItemByIdOrSlug(projectSlug, wiIdOrSlug);
285
292
  if (!wi)
286
293
  return null;
287
- if (wi.status !== 'active')
294
+ if (wi.status === 'planning' || wi.status === 'done')
295
+ return wi;
296
+ const backlog = wi.columns['backlog'].length;
297
+ const todo = wi.columns['todo'].length;
298
+ const inProgress = wi.columns['in-progress'].length;
299
+ const review = wi.columns['review'].length;
300
+ const done = wi.columns['done'].length;
301
+ const total = backlog + todo + inProgress + review + done;
302
+ const pending = backlog + todo + inProgress;
303
+ if (total === 0)
288
304
  return wi;
289
- const pendingCount = wi.columns['backlog'].length +
290
- wi.columns['todo'].length +
291
- wi.columns['in-progress'].length;
292
- const totalTasks = pendingCount +
293
- wi.columns['review'].length +
294
- wi.columns['done'].length;
295
- // Only promote if there are tasks and none are pending
296
- if (totalTasks > 0 && pendingCount === 0) {
297
- wi.status = 'review';
305
+ let newStatus = wi.status;
306
+ if (done === total) {
307
+ // ALL tasks done → work item done
308
+ newStatus = 'done';
309
+ }
310
+ else if (pending === 0) {
311
+ // No pending tasks (all in review/done) work item review
312
+ newStatus = 'review';
313
+ }
314
+ else if (wi.status === 'review' && pending > 0) {
315
+ // Tasks moved back to pending → revert to active
316
+ newStatus = 'active';
317
+ }
318
+ if (newStatus !== wi.status) {
319
+ wi.status = newStatus;
298
320
  wi.updatedAt = new Date().toISOString();
299
321
  writeWorkItem(projectSlug, wi);
300
322
  }
301
323
  return wi;
302
324
  }
325
+ /** @deprecated Use syncWorkItemStatus instead */
326
+ function promoteWorkItemIfComplete(projectSlug, wiIdOrSlug) {
327
+ return syncWorkItemStatus(projectSlug, wiIdOrSlug);
328
+ }
329
+ /**
330
+ * Reorder a work item within its status column.
331
+ */
332
+ function reorderWorkItem(projectSlug, wiIdOrSlug, newOrder) {
333
+ const wi = findWorkItemByIdOrSlug(projectSlug, wiIdOrSlug);
334
+ if (!wi)
335
+ return null;
336
+ wi.order = newOrder;
337
+ wi.updatedAt = new Date().toISOString();
338
+ writeWorkItem(projectSlug, wi);
339
+ return wi;
340
+ }
303
341
  // --- Utilities ---
304
342
  function getTaskCounts(wi) {
305
343
  const counts = {};
@@ -317,4 +355,3 @@ function getProgress(wi) {
317
355
  }
318
356
  return { completed, total, percent: total === 0 ? 0 : Math.round((completed / total) * 100) };
319
357
  }
320
- //# sourceMappingURL=workItemStore.js.map