@devran-ai/kit 4.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 (231) hide show
  1. package/.agent/CheatSheet.md +350 -0
  2. package/.agent/README.md +76 -0
  3. package/.agent/agents/README.md +155 -0
  4. package/.agent/agents/architect.md +185 -0
  5. package/.agent/agents/backend-specialist.md +276 -0
  6. package/.agent/agents/build-error-resolver.md +207 -0
  7. package/.agent/agents/code-reviewer.md +162 -0
  8. package/.agent/agents/database-architect.md +138 -0
  9. package/.agent/agents/devops-engineer.md +144 -0
  10. package/.agent/agents/doc-updater.md +229 -0
  11. package/.agent/agents/e2e-runner.md +145 -0
  12. package/.agent/agents/explorer-agent.md +143 -0
  13. package/.agent/agents/frontend-specialist.md +144 -0
  14. package/.agent/agents/go-reviewer.md +128 -0
  15. package/.agent/agents/knowledge-agent.md +197 -0
  16. package/.agent/agents/mobile-developer.md +150 -0
  17. package/.agent/agents/performance-optimizer.md +175 -0
  18. package/.agent/agents/planner.md +133 -0
  19. package/.agent/agents/pr-reviewer.md +148 -0
  20. package/.agent/agents/python-reviewer.md +123 -0
  21. package/.agent/agents/refactor-cleaner.md +201 -0
  22. package/.agent/agents/reliability-engineer.md +156 -0
  23. package/.agent/agents/security-reviewer.md +141 -0
  24. package/.agent/agents/sprint-orchestrator.md +124 -0
  25. package/.agent/agents/tdd-guide.md +179 -0
  26. package/.agent/agents/typescript-reviewer.md +110 -0
  27. package/.agent/checklists/README.md +102 -0
  28. package/.agent/checklists/pre-commit.md +93 -0
  29. package/.agent/checklists/session-end.md +99 -0
  30. package/.agent/checklists/session-start.md +102 -0
  31. package/.agent/checklists/task-complete.md +81 -0
  32. package/.agent/commands/README.md +130 -0
  33. package/.agent/commands/adr.md +29 -0
  34. package/.agent/commands/ask.md +28 -0
  35. package/.agent/commands/build.md +30 -0
  36. package/.agent/commands/changelog.md +40 -0
  37. package/.agent/commands/checkpoint.md +28 -0
  38. package/.agent/commands/code-review.md +65 -0
  39. package/.agent/commands/compact.md +28 -0
  40. package/.agent/commands/cook.md +30 -0
  41. package/.agent/commands/db.md +30 -0
  42. package/.agent/commands/debug.md +31 -0
  43. package/.agent/commands/deploy.md +37 -0
  44. package/.agent/commands/design.md +29 -0
  45. package/.agent/commands/doc.md +30 -0
  46. package/.agent/commands/eval.md +30 -0
  47. package/.agent/commands/fix.md +32 -0
  48. package/.agent/commands/git.md +32 -0
  49. package/.agent/commands/help.md +273 -0
  50. package/.agent/commands/implement.md +30 -0
  51. package/.agent/commands/integrate.md +32 -0
  52. package/.agent/commands/learn.md +29 -0
  53. package/.agent/commands/perf.md +31 -0
  54. package/.agent/commands/plan.md +56 -0
  55. package/.agent/commands/pr-describe.md +65 -0
  56. package/.agent/commands/pr-fix.md +45 -0
  57. package/.agent/commands/pr-merge.md +45 -0
  58. package/.agent/commands/pr-review.md +50 -0
  59. package/.agent/commands/pr-split.md +54 -0
  60. package/.agent/commands/pr-status.md +56 -0
  61. package/.agent/commands/pr.md +58 -0
  62. package/.agent/commands/refactor.md +32 -0
  63. package/.agent/commands/research.md +28 -0
  64. package/.agent/commands/scout.md +30 -0
  65. package/.agent/commands/security-scan.md +33 -0
  66. package/.agent/commands/setup.md +31 -0
  67. package/.agent/commands/status.md +59 -0
  68. package/.agent/commands/tdd.md +73 -0
  69. package/.agent/commands/verify.md +58 -0
  70. package/.agent/contexts/brainstorm.md +26 -0
  71. package/.agent/contexts/debug.md +28 -0
  72. package/.agent/contexts/implement.md +29 -0
  73. package/.agent/contexts/plan-quality-log.md +30 -0
  74. package/.agent/contexts/review.md +27 -0
  75. package/.agent/contexts/ship.md +28 -0
  76. package/.agent/decisions/001-trust-grade-governance.md +46 -0
  77. package/.agent/decisions/002-cross-ide-generation.md +15 -0
  78. package/.agent/engine/identity.json +4 -0
  79. package/.agent/engine/loading-rules.json +193 -0
  80. package/.agent/engine/marketplace-index.json +29 -0
  81. package/.agent/engine/mcp-servers/filesystem.json +9 -0
  82. package/.agent/engine/mcp-servers/github.json +11 -0
  83. package/.agent/engine/mcp-servers/postgres.json +11 -0
  84. package/.agent/engine/mcp-servers/supabase.json +11 -0
  85. package/.agent/engine/mcp-servers/vercel.json +11 -0
  86. package/.agent/engine/reliability-config.json +14 -0
  87. package/.agent/engine/sdlc-map.json +50 -0
  88. package/.agent/engine/workflow-state.json +167 -0
  89. package/.agent/hooks/README.md +101 -0
  90. package/.agent/hooks/hooks.json +104 -0
  91. package/.agent/hooks/templates/session-end.md +110 -0
  92. package/.agent/hooks/templates/session-start.md +95 -0
  93. package/.agent/manifest.json +466 -0
  94. package/.agent/rules/agent-upgrade-policy.md +56 -0
  95. package/.agent/rules/architecture.md +111 -0
  96. package/.agent/rules/coding-style.md +75 -0
  97. package/.agent/rules/documentation.md +74 -0
  98. package/.agent/rules/git-workflow.md +140 -0
  99. package/.agent/rules/quality-gate.md +117 -0
  100. package/.agent/rules/security.md +67 -0
  101. package/.agent/rules/sprint-tracking.md +103 -0
  102. package/.agent/rules/testing.md +80 -0
  103. package/.agent/rules/workflow-standards.md +30 -0
  104. package/.agent/rules.md +293 -0
  105. package/.agent/session-context.md +69 -0
  106. package/.agent/session-state.json +27 -0
  107. package/.agent/skills/README.md +135 -0
  108. package/.agent/skills/api-patterns/SKILL.md +117 -0
  109. package/.agent/skills/app-builder/SKILL.md +202 -0
  110. package/.agent/skills/architecture/SKILL.md +101 -0
  111. package/.agent/skills/behavioral-modes/SKILL.md +295 -0
  112. package/.agent/skills/brainstorming/SKILL.md +156 -0
  113. package/.agent/skills/clean-code/SKILL.md +142 -0
  114. package/.agent/skills/context-budget/SKILL.md +78 -0
  115. package/.agent/skills/continuous-learning/SKILL.md +145 -0
  116. package/.agent/skills/database-design/SKILL.md +303 -0
  117. package/.agent/skills/debugging-strategies/SKILL.md +158 -0
  118. package/.agent/skills/deployment-procedures/SKILL.md +191 -0
  119. package/.agent/skills/docker-patterns/SKILL.md +161 -0
  120. package/.agent/skills/eval-harness/SKILL.md +89 -0
  121. package/.agent/skills/frontend-patterns/SKILL.md +141 -0
  122. package/.agent/skills/git-workflow/SKILL.md +159 -0
  123. package/.agent/skills/i18n-localization/SKILL.md +191 -0
  124. package/.agent/skills/intelligent-routing/SKILL.md +180 -0
  125. package/.agent/skills/mcp-integration/SKILL.md +240 -0
  126. package/.agent/skills/mobile-design/SKILL.md +191 -0
  127. package/.agent/skills/nodejs-patterns/SKILL.md +164 -0
  128. package/.agent/skills/parallel-agents/SKILL.md +200 -0
  129. package/.agent/skills/performance-profiling/SKILL.md +134 -0
  130. package/.agent/skills/plan-validation/SKILL.md +192 -0
  131. package/.agent/skills/plan-writing/SKILL.md +183 -0
  132. package/.agent/skills/plan-writing/domain-enhancers.md +184 -0
  133. package/.agent/skills/plan-writing/plan-retrospective.md +116 -0
  134. package/.agent/skills/plan-writing/plan-schema.md +119 -0
  135. package/.agent/skills/pr-toolkit/SKILL.md +174 -0
  136. package/.agent/skills/production-readiness/SKILL.md +126 -0
  137. package/.agent/skills/security-practices/SKILL.md +109 -0
  138. package/.agent/skills/shell-conventions/SKILL.md +92 -0
  139. package/.agent/skills/strategic-compact/SKILL.md +62 -0
  140. package/.agent/skills/testing-patterns/SKILL.md +141 -0
  141. package/.agent/skills/typescript-expert/SKILL.md +160 -0
  142. package/.agent/skills/ui-ux-pro-max/SKILL.md +137 -0
  143. package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
  144. package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
  145. package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
  146. package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
  147. package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
  148. package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  149. package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  150. package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  151. package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  152. package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  153. package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  154. package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  155. package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  156. package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  157. package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  158. package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  159. package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  160. package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  161. package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  162. package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -0
  163. package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
  164. package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  165. package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  166. package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  167. package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -0
  168. package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  169. package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -0
  170. package/.agent/skills/verification-loop/SKILL.md +89 -0
  171. package/.agent/skills/webapp-testing/SKILL.md +175 -0
  172. package/.agent/templates/adr-template.md +32 -0
  173. package/.agent/templates/bug-report.md +37 -0
  174. package/.agent/templates/feature-request.md +32 -0
  175. package/.agent/workflows/README.md +101 -0
  176. package/.agent/workflows/brainstorm.md +86 -0
  177. package/.agent/workflows/create.md +85 -0
  178. package/.agent/workflows/debug.md +83 -0
  179. package/.agent/workflows/deploy.md +114 -0
  180. package/.agent/workflows/enhance.md +85 -0
  181. package/.agent/workflows/orchestrate.md +106 -0
  182. package/.agent/workflows/plan.md +105 -0
  183. package/.agent/workflows/pr-fix.md +163 -0
  184. package/.agent/workflows/pr-merge.md +117 -0
  185. package/.agent/workflows/pr-review.md +178 -0
  186. package/.agent/workflows/pr-split.md +118 -0
  187. package/.agent/workflows/pr.md +184 -0
  188. package/.agent/workflows/preflight.md +107 -0
  189. package/.agent/workflows/preview.md +95 -0
  190. package/.agent/workflows/quality-gate.md +103 -0
  191. package/.agent/workflows/retrospective.md +100 -0
  192. package/.agent/workflows/review.md +104 -0
  193. package/.agent/workflows/status.md +89 -0
  194. package/.agent/workflows/test.md +98 -0
  195. package/.agent/workflows/ui-ux-pro-max.md +93 -0
  196. package/.agent/workflows/upgrade.md +97 -0
  197. package/LICENSE +21 -0
  198. package/README.md +218 -0
  199. package/bin/kit.js +773 -0
  200. package/lib/agent-registry.js +228 -0
  201. package/lib/agent-reputation.js +343 -0
  202. package/lib/circuit-breaker.js +195 -0
  203. package/lib/cli-commands.js +322 -0
  204. package/lib/config-validator.js +274 -0
  205. package/lib/conflict-detector.js +252 -0
  206. package/lib/constants.js +47 -0
  207. package/lib/engineering-manager.js +336 -0
  208. package/lib/error-budget.js +370 -0
  209. package/lib/hook-system.js +256 -0
  210. package/lib/ide-generator.js +434 -0
  211. package/lib/identity.js +240 -0
  212. package/lib/io.js +146 -0
  213. package/lib/learning-engine.js +163 -0
  214. package/lib/loading-engine.js +421 -0
  215. package/lib/logger.js +118 -0
  216. package/lib/marketplace.js +321 -0
  217. package/lib/plugin-system.js +604 -0
  218. package/lib/plugin-verifier.js +197 -0
  219. package/lib/rate-limiter.js +113 -0
  220. package/lib/security-scanner.js +312 -0
  221. package/lib/self-healing.js +468 -0
  222. package/lib/session-manager.js +264 -0
  223. package/lib/skill-sandbox.js +244 -0
  224. package/lib/task-governance.js +522 -0
  225. package/lib/task-model.js +332 -0
  226. package/lib/updater.js +240 -0
  227. package/lib/verify.js +279 -0
  228. package/lib/workflow-engine.js +373 -0
  229. package/lib/workflow-events.js +166 -0
  230. package/lib/workflow-persistence.js +160 -0
  231. package/package.json +57 -0
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Devran AI Kit — Task Data Model
3
+ *
4
+ * JSON file-backed task CRUD with status FSM tracking.
5
+ * Provides the data layer for task governance.
6
+ *
7
+ * @module lib/task-model
8
+ * @author Emre Dursun
9
+ * @since v3.0.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const crypto = require('crypto');
17
+
18
+ const { AGENT_DIR, ENGINE_DIR } = require('./constants');
19
+ const { writeJsonAtomic } = require('./io');
20
+ const TASKS_FILE = 'tasks.json';
21
+
22
+ /** Valid task status values */
23
+ const VALID_STATUSES = ['open', 'in-progress', 'review', 'done', 'blocked'];
24
+
25
+ /** Valid status transitions */
26
+ const STATUS_TRANSITIONS = {
27
+ 'open': ['in-progress', 'blocked'],
28
+ 'in-progress': ['review', 'blocked', 'open'],
29
+ 'review': ['done', 'in-progress'],
30
+ 'done': ['open'],
31
+ 'blocked': ['open', 'in-progress'],
32
+ };
33
+
34
+ /** Valid priority levels */
35
+ const VALID_PRIORITIES = ['critical', 'high', 'medium', 'low'];
36
+
37
+ /**
38
+ * @typedef {object} Task
39
+ * @property {string} id - Unique task ID
40
+ * @property {string} title - Task title
41
+ * @property {string | null} description - Optional description
42
+ * @property {string} status - Current status
43
+ * @property {string} priority - Priority level
44
+ * @property {string | null} assignee - Assigned agent or person
45
+ * @property {string} createdAt - ISO timestamp
46
+ * @property {string} updatedAt - ISO timestamp
47
+ * @property {string | null} completedAt - Completion timestamp
48
+ * @property {boolean} deleted - Soft delete flag
49
+ */
50
+
51
+ /**
52
+ * Resolves the tasks file path.
53
+ *
54
+ * @param {string} projectRoot - Root directory of the project
55
+ * @returns {string} Absolute path to tasks.json
56
+ */
57
+ function resolveTasksPath(projectRoot) {
58
+ return path.join(projectRoot, AGENT_DIR, ENGINE_DIR, TASKS_FILE);
59
+ }
60
+
61
+ /**
62
+ * Loads all tasks from disk.
63
+ *
64
+ * @param {string} projectRoot - Root directory of the project
65
+ * @returns {Task[]}
66
+ */
67
+ function loadTasks(projectRoot) {
68
+ const tasksPath = resolveTasksPath(projectRoot);
69
+
70
+ if (!fs.existsSync(tasksPath)) {
71
+ return [];
72
+ }
73
+
74
+ try {
75
+ const data = JSON.parse(fs.readFileSync(tasksPath, 'utf-8'));
76
+ return data.tasks || [];
77
+ } catch {
78
+ return [];
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Writes tasks to disk atomically.
84
+ *
85
+ * @param {string} projectRoot - Root directory of the project
86
+ * @param {Task[]} tasks - Tasks array
87
+ * @returns {void}
88
+ */
89
+ function writeTasks(projectRoot, tasks) {
90
+ const tasksPath = resolveTasksPath(projectRoot);
91
+
92
+ const data = {
93
+ schemaVersion: '1.0.0',
94
+ lastUpdated: new Date().toISOString(),
95
+ tasks,
96
+ };
97
+
98
+ writeJsonAtomic(tasksPath, data);
99
+ }
100
+
101
+ /**
102
+ * Creates a new task.
103
+ *
104
+ * @param {string} projectRoot - Root directory of the project
105
+ * @param {object} params - Task parameters
106
+ * @param {string} params.title - Task title
107
+ * @param {string} [params.description] - Optional description
108
+ * @param {string} [params.assignee] - Optional assignee
109
+ * @param {string} [params.priority] - Priority (default: 'medium')
110
+ * @returns {Task}
111
+ */
112
+ function createTask(projectRoot, { title, description, assignee, priority }) {
113
+ const tasks = loadTasks(projectRoot);
114
+ const now = new Date().toISOString();
115
+
116
+ const taskPriority = priority && VALID_PRIORITIES.includes(priority) ? priority : 'medium';
117
+
118
+ /** @type {Task} */
119
+ const task = {
120
+ id: `TSK-${crypto.randomUUID().slice(0, 8).toUpperCase()}`,
121
+ title,
122
+ description: description || null,
123
+ status: 'open',
124
+ priority: taskPriority,
125
+ assignee: assignee || null,
126
+ createdAt: now,
127
+ updatedAt: now,
128
+ completedAt: null,
129
+ deleted: false,
130
+ };
131
+
132
+ writeTasks(projectRoot, [...tasks, task]);
133
+
134
+ return task;
135
+ }
136
+
137
+ /**
138
+ * Retrieves a task by ID.
139
+ *
140
+ * @param {string} projectRoot - Root directory of the project
141
+ * @param {string} taskId - Task ID
142
+ * @returns {Task | null}
143
+ */
144
+ function getTask(projectRoot, taskId) {
145
+ const tasks = loadTasks(projectRoot);
146
+ return tasks.find((t) => t.id === taskId && !t.deleted) || null;
147
+ }
148
+
149
+ /**
150
+ * Lists tasks with optional filters.
151
+ *
152
+ * @param {string} projectRoot - Root directory of the project
153
+ * @param {object} [filters] - Filter options
154
+ * @param {string} [filters.status] - Filter by status
155
+ * @param {string} [filters.priority] - Filter by priority
156
+ * @param {string} [filters.assignee] - Filter by assignee
157
+ * @returns {Task[]}
158
+ */
159
+ function listTasks(projectRoot, filters = {}) {
160
+ let tasks = loadTasks(projectRoot).filter((t) => !t.deleted);
161
+
162
+ if (filters.status) {
163
+ tasks = tasks.filter((t) => t.status === filters.status);
164
+ }
165
+ if (filters.priority) {
166
+ tasks = tasks.filter((t) => t.priority === filters.priority);
167
+ }
168
+ if (filters.assignee) {
169
+ tasks = tasks.filter((t) => t.assignee === filters.assignee);
170
+ }
171
+
172
+ return tasks;
173
+ }
174
+
175
+ /**
176
+ * Updates a task's fields.
177
+ *
178
+ * @param {string} projectRoot - Root directory of the project
179
+ * @param {string} taskId - Task ID
180
+ * @param {object} updates - Fields to update
181
+ * @returns {{ success: boolean, task: Task | null }}
182
+ */
183
+ function updateTask(projectRoot, taskId, updates) {
184
+ const tasks = loadTasks(projectRoot);
185
+ const taskIndex = tasks.findIndex((t) => t.id === taskId && !t.deleted);
186
+
187
+ if (taskIndex === -1) {
188
+ return { success: false, task: null };
189
+ }
190
+
191
+ const allowedFields = ['title', 'description', 'assignee', 'priority'];
192
+ const fieldUpdates = {};
193
+ for (const field of allowedFields) {
194
+ if (updates[field] !== undefined) {
195
+ fieldUpdates[field] = updates[field];
196
+ }
197
+ }
198
+
199
+ const updatedTask = { ...tasks[taskIndex], ...fieldUpdates, updatedAt: new Date().toISOString() };
200
+ const updatedTasks = tasks.map((t, i) => (i === taskIndex ? updatedTask : t));
201
+ writeTasks(projectRoot, updatedTasks);
202
+
203
+ return { success: true, task: updatedTask };
204
+ }
205
+
206
+ /**
207
+ * Transitions a task to a new status following the FSM.
208
+ *
209
+ * @param {string} projectRoot - Root directory of the project
210
+ * @param {string} taskId - Task ID
211
+ * @param {string} newStatus - Target status
212
+ * @returns {{ success: boolean, error?: string }}
213
+ */
214
+ function transitionTask(projectRoot, taskId, newStatus) {
215
+ if (!VALID_STATUSES.includes(newStatus)) {
216
+ return { success: false, error: `Invalid status: ${newStatus}. Valid: ${VALID_STATUSES.join(', ')}` };
217
+ }
218
+
219
+ const tasks = loadTasks(projectRoot);
220
+ const taskIndex = tasks.findIndex((t) => t.id === taskId && !t.deleted);
221
+
222
+ if (taskIndex === -1) {
223
+ return { success: false, error: `Task not found: ${taskId}` };
224
+ }
225
+
226
+ const currentStatus = tasks[taskIndex].status;
227
+ const allowed = STATUS_TRANSITIONS[currentStatus] || [];
228
+
229
+ if (!allowed.includes(newStatus)) {
230
+ return {
231
+ success: false,
232
+ error: `Invalid transition: ${currentStatus} → ${newStatus}. Allowed: [${allowed.join(', ')}]`,
233
+ };
234
+ }
235
+
236
+ const now = new Date().toISOString();
237
+ const updatedTask = {
238
+ ...tasks[taskIndex],
239
+ status: newStatus,
240
+ updatedAt: now,
241
+ completedAt: newStatus === 'done' ? now : tasks[taskIndex].completedAt,
242
+ };
243
+ const updatedTasks = tasks.map((t, i) => (i === taskIndex ? updatedTask : t));
244
+ writeTasks(projectRoot, updatedTasks);
245
+
246
+ return { success: true };
247
+ }
248
+
249
+ /**
250
+ * Soft-deletes a task.
251
+ *
252
+ * @param {string} projectRoot - Root directory of the project
253
+ * @param {string} taskId - Task ID
254
+ * @returns {{ success: boolean }}
255
+ */
256
+ function deleteTask(projectRoot, taskId) {
257
+ const tasks = loadTasks(projectRoot);
258
+ const taskIndex = tasks.findIndex((t) => t.id === taskId);
259
+
260
+ if (taskIndex === -1) {
261
+ return { success: false };
262
+ }
263
+
264
+ const updatedTasks = tasks.map((t, i) =>
265
+ i === taskIndex ? { ...t, deleted: true, updatedAt: new Date().toISOString() } : t
266
+ );
267
+ writeTasks(projectRoot, updatedTasks);
268
+
269
+ return { success: true };
270
+ }
271
+
272
+ /**
273
+ * Returns task metrics: counts by status, avg cycle time.
274
+ *
275
+ * @param {string} projectRoot - Root directory of the project
276
+ * @returns {object} Task metrics
277
+ */
278
+ function getTaskMetrics(projectRoot) {
279
+ const tasks = loadTasks(projectRoot).filter((t) => !t.deleted);
280
+
281
+ const counts = {};
282
+ for (const status of VALID_STATUSES) {
283
+ counts[status] = tasks.filter((t) => t.status === status).length;
284
+ }
285
+
286
+ // Calculate average cycle time for completed tasks
287
+ const completedTasks = tasks.filter((t) => t.status === 'done' && t.completedAt);
288
+ let avgCycleTimeSeconds = 0;
289
+
290
+ if (completedTasks.length > 0) {
291
+ const totalCycleTime = completedTasks.reduce((sum, t) => {
292
+ return sum + (new Date(t.completedAt).getTime() - new Date(t.createdAt).getTime());
293
+ }, 0);
294
+ avgCycleTimeSeconds = Math.round(totalCycleTime / completedTasks.length / 1000);
295
+ }
296
+
297
+ return {
298
+ total: tasks.length,
299
+ counts,
300
+ avgCycleTimeSeconds,
301
+ completionRate: tasks.length > 0 ? Math.round((counts.done / tasks.length) * 100) : 0,
302
+ };
303
+ }
304
+
305
+ /** Priority sort order: critical > high > medium > low */
306
+ const PRIORITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
307
+
308
+ /**
309
+ * Sorts tasks by priority (critical first, low last).
310
+ *
311
+ * @param {Task[]} tasks - Tasks to sort
312
+ * @returns {Task[]} New sorted array (does not mutate input)
313
+ */
314
+ function sortByPriority(tasks) {
315
+ return [...tasks].sort((a, b) => {
316
+ const aPriority = PRIORITY_ORDER[a.priority] ?? 2;
317
+ const bPriority = PRIORITY_ORDER[b.priority] ?? 2;
318
+ return aPriority - bPriority;
319
+ });
320
+ }
321
+
322
+ module.exports = {
323
+ createTask,
324
+ getTask,
325
+ listTasks,
326
+ updateTask,
327
+ transitionTask,
328
+ deleteTask,
329
+ getTaskMetrics,
330
+ sortByPriority,
331
+ PRIORITY_ORDER,
332
+ };
package/lib/updater.js ADDED
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Devran AI Kit — CLI Updater
3
+ *
4
+ * Non-destructive update mechanism that merges new framework
5
+ * files into an existing .agent/ installation while preserving
6
+ * user customizations.
7
+ *
8
+ * @module lib/updater
9
+ * @author Emre Dursun
10
+ * @since v3.0.0
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const crypto = require('crypto');
18
+
19
+ const { AGENT_DIR } = require('./constants');
20
+
21
+ /**
22
+ * User data files that should NEVER be overwritten during updates.
23
+ * These contain project-specific data populated at runtime.
24
+ */
25
+ const USER_DATA_FILES = new Set([
26
+ 'session-context.md',
27
+ 'session-state.json',
28
+ 'engine/identity.json',
29
+ ]);
30
+
31
+ /**
32
+ * User data directories whose contents should NEVER be overwritten.
33
+ * These contain project-specific decisions, context, and customizations.
34
+ *
35
+ * Must stay aligned with the Preservation Contract in:
36
+ * .agent/rules/agent-upgrade-policy.md § 1
37
+ */
38
+ const USER_DATA_DIRS = new Set([
39
+ 'decisions',
40
+ 'contexts',
41
+ 'checklists',
42
+ 'rules',
43
+ ]);
44
+
45
+ /**
46
+ * @typedef {object} UpdateReport
47
+ * @property {string[]} added - Files that were added (new in source)
48
+ * @property {string[]} updated - Files that were updated (hash mismatch)
49
+ * @property {string[]} skipped - Files that were preserved (user customizations)
50
+ * @property {string[]} unchanged - Files that are identical
51
+ */
52
+
53
+ /**
54
+ * Computes a SHA-256 hash of a file's contents.
55
+ *
56
+ * @param {string} filePath - Absolute path to the file
57
+ * @returns {string} Hex-encoded SHA-256 hash
58
+ */
59
+ function fileHash(filePath) {
60
+ const content = fs.readFileSync(filePath);
61
+ return crypto.createHash('sha256').update(content).digest('hex');
62
+ }
63
+
64
+ /**
65
+ * Recursively collects all file paths relative to a root directory.
66
+ *
67
+ * @param {string} rootDir - Root directory to scan
68
+ * @param {string} [prefix=''] - Path prefix for recursion
69
+ * @returns {string[]} Array of relative paths
70
+ */
71
+ function collectFiles(rootDir, prefix = '') {
72
+ /** @type {string[]} */
73
+ const files = [];
74
+
75
+ if (!fs.existsSync(rootDir)) {
76
+ return files;
77
+ }
78
+
79
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
80
+
81
+ for (const entry of entries) {
82
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
83
+
84
+ if (entry.isDirectory()) {
85
+ files.push(...collectFiles(path.join(rootDir, entry.name), relativePath));
86
+ } else {
87
+ files.push(relativePath);
88
+ }
89
+ }
90
+
91
+ return files;
92
+ }
93
+
94
+ /**
95
+ * Determines if a relative file path is in the preserved set.
96
+ *
97
+ * @param {string} relativePath - Relative path from .agent/ root
98
+ * @returns {boolean} True if the file should be preserved
99
+ */
100
+ function isPreservedFile(relativePath) {
101
+ // Normalize path separators for cross-platform matching
102
+ const normalizedPath = relativePath.replace(/\\/g, '/');
103
+
104
+ // Check exact relative path matches (e.g., 'engine/identity.json')
105
+ if (USER_DATA_FILES.has(normalizedPath)) {
106
+ return true;
107
+ }
108
+
109
+ // Check basename matches for root-level files (e.g., 'session-context.md')
110
+ const basename = path.basename(relativePath);
111
+ if (USER_DATA_FILES.has(basename)) {
112
+ return true;
113
+ }
114
+
115
+ // Check if inside a user data directory
116
+ const parts = normalizedPath.split('/');
117
+ for (const dir of USER_DATA_DIRS) {
118
+ if (parts.includes(dir)) {
119
+ return true;
120
+ }
121
+ }
122
+
123
+ return false;
124
+ }
125
+
126
+ /**
127
+ * Generates a diff report comparing source and target .agent/ directories.
128
+ *
129
+ * @param {string} sourceRoot - Root of the package (source .agent/ location)
130
+ * @param {string} targetRoot - Root of the project (target .agent/ location)
131
+ * @returns {UpdateReport}
132
+ */
133
+ function generateDiff(sourceRoot, targetRoot) {
134
+ const sourceDir = path.join(sourceRoot, AGENT_DIR);
135
+ const targetDir = path.join(targetRoot, AGENT_DIR);
136
+
137
+ const sourceFiles = collectFiles(sourceDir);
138
+ const targetFiles = new Set(collectFiles(targetDir));
139
+
140
+ /** @type {UpdateReport} */
141
+ const report = {
142
+ added: [],
143
+ updated: [],
144
+ skipped: [],
145
+ unchanged: [],
146
+ };
147
+
148
+ for (const relativeFile of sourceFiles) {
149
+ const sourcePath = path.join(sourceDir, relativeFile);
150
+ const targetPath = path.join(targetDir, relativeFile);
151
+
152
+ if (isPreservedFile(relativeFile) && targetFiles.has(relativeFile)) {
153
+ report.skipped.push(relativeFile);
154
+ continue;
155
+ }
156
+
157
+ if (!targetFiles.has(relativeFile)) {
158
+ report.added.push(relativeFile);
159
+ continue;
160
+ }
161
+
162
+ const sourceHash = fileHash(sourcePath);
163
+ const targetHash = fileHash(targetPath);
164
+
165
+ if (sourceHash !== targetHash) {
166
+ report.updated.push(relativeFile);
167
+ } else {
168
+ report.unchanged.push(relativeFile);
169
+ }
170
+ }
171
+
172
+ return report;
173
+ }
174
+
175
+ /**
176
+ * Applies a non-destructive update from source to target.
177
+ *
178
+ * @param {string} sourceRoot - Root of the package
179
+ * @param {string} targetRoot - Root of the project
180
+ * @param {boolean} [dryRun=false] - If true, report only without modifying
181
+ * @returns {UpdateReport}
182
+ */
183
+ function applyUpdate(sourceRoot, targetRoot, dryRun = false) {
184
+ const report = generateDiff(sourceRoot, targetRoot);
185
+
186
+ if (dryRun) {
187
+ return report;
188
+ }
189
+
190
+ const sourceDir = path.join(sourceRoot, AGENT_DIR);
191
+ const targetDir = path.join(targetRoot, AGENT_DIR);
192
+
193
+ // Copy new files
194
+ for (const relativeFile of report.added) {
195
+ const sourcePath = path.join(sourceDir, relativeFile);
196
+ const targetPath = path.join(targetDir, relativeFile);
197
+ const targetDirPath = path.dirname(targetPath);
198
+
199
+ if (!fs.existsSync(targetDirPath)) {
200
+ fs.mkdirSync(targetDirPath, { recursive: true });
201
+ }
202
+
203
+ fs.copyFileSync(sourcePath, targetPath);
204
+ }
205
+
206
+ // Update modified files
207
+ for (const relativeFile of report.updated) {
208
+ const sourcePath = path.join(sourceDir, relativeFile);
209
+ const targetPath = path.join(targetDir, relativeFile);
210
+
211
+ fs.copyFileSync(sourcePath, targetPath);
212
+ }
213
+
214
+ // Regenerate IDE configs after .agent/ files are updated
215
+ try {
216
+ const { generateAllIdeConfigs, writeIdeConfigs } = require('./ide-generator');
217
+ const manifestPath = path.join(targetDir, 'manifest.json');
218
+ const rulesPath = path.join(targetRoot, AGENT_DIR, 'rules.md');
219
+ if (fs.existsSync(manifestPath) && fs.existsSync(rulesPath)) {
220
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
221
+ const rulesContent = fs.readFileSync(rulesPath, 'utf-8');
222
+ const configs = generateAllIdeConfigs(manifest, rulesContent);
223
+ writeIdeConfigs(targetRoot, configs, { skipExisting: true });
224
+ }
225
+ } catch (err) {
226
+ console.error(` Error regenerating IDE configs: ${err.message}`);
227
+ // Non-fatal — IDE configs are optional
228
+ }
229
+
230
+ return report;
231
+ }
232
+
233
+ module.exports = {
234
+ generateDiff,
235
+ applyUpdate,
236
+ isPreservedFile,
237
+ collectFiles,
238
+ USER_DATA_FILES,
239
+ USER_DATA_DIRS,
240
+ };