@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.
- package/.agent/CheatSheet.md +350 -0
- package/.agent/README.md +76 -0
- package/.agent/agents/README.md +155 -0
- package/.agent/agents/architect.md +185 -0
- package/.agent/agents/backend-specialist.md +276 -0
- package/.agent/agents/build-error-resolver.md +207 -0
- package/.agent/agents/code-reviewer.md +162 -0
- package/.agent/agents/database-architect.md +138 -0
- package/.agent/agents/devops-engineer.md +144 -0
- package/.agent/agents/doc-updater.md +229 -0
- package/.agent/agents/e2e-runner.md +145 -0
- package/.agent/agents/explorer-agent.md +143 -0
- package/.agent/agents/frontend-specialist.md +144 -0
- package/.agent/agents/go-reviewer.md +128 -0
- package/.agent/agents/knowledge-agent.md +197 -0
- package/.agent/agents/mobile-developer.md +150 -0
- package/.agent/agents/performance-optimizer.md +175 -0
- package/.agent/agents/planner.md +133 -0
- package/.agent/agents/pr-reviewer.md +148 -0
- package/.agent/agents/python-reviewer.md +123 -0
- package/.agent/agents/refactor-cleaner.md +201 -0
- package/.agent/agents/reliability-engineer.md +156 -0
- package/.agent/agents/security-reviewer.md +141 -0
- package/.agent/agents/sprint-orchestrator.md +124 -0
- package/.agent/agents/tdd-guide.md +179 -0
- package/.agent/agents/typescript-reviewer.md +110 -0
- package/.agent/checklists/README.md +102 -0
- package/.agent/checklists/pre-commit.md +93 -0
- package/.agent/checklists/session-end.md +99 -0
- package/.agent/checklists/session-start.md +102 -0
- package/.agent/checklists/task-complete.md +81 -0
- package/.agent/commands/README.md +130 -0
- package/.agent/commands/adr.md +29 -0
- package/.agent/commands/ask.md +28 -0
- package/.agent/commands/build.md +30 -0
- package/.agent/commands/changelog.md +40 -0
- package/.agent/commands/checkpoint.md +28 -0
- package/.agent/commands/code-review.md +65 -0
- package/.agent/commands/compact.md +28 -0
- package/.agent/commands/cook.md +30 -0
- package/.agent/commands/db.md +30 -0
- package/.agent/commands/debug.md +31 -0
- package/.agent/commands/deploy.md +37 -0
- package/.agent/commands/design.md +29 -0
- package/.agent/commands/doc.md +30 -0
- package/.agent/commands/eval.md +30 -0
- package/.agent/commands/fix.md +32 -0
- package/.agent/commands/git.md +32 -0
- package/.agent/commands/help.md +273 -0
- package/.agent/commands/implement.md +30 -0
- package/.agent/commands/integrate.md +32 -0
- package/.agent/commands/learn.md +29 -0
- package/.agent/commands/perf.md +31 -0
- package/.agent/commands/plan.md +56 -0
- package/.agent/commands/pr-describe.md +65 -0
- package/.agent/commands/pr-fix.md +45 -0
- package/.agent/commands/pr-merge.md +45 -0
- package/.agent/commands/pr-review.md +50 -0
- package/.agent/commands/pr-split.md +54 -0
- package/.agent/commands/pr-status.md +56 -0
- package/.agent/commands/pr.md +58 -0
- package/.agent/commands/refactor.md +32 -0
- package/.agent/commands/research.md +28 -0
- package/.agent/commands/scout.md +30 -0
- package/.agent/commands/security-scan.md +33 -0
- package/.agent/commands/setup.md +31 -0
- package/.agent/commands/status.md +59 -0
- package/.agent/commands/tdd.md +73 -0
- package/.agent/commands/verify.md +58 -0
- package/.agent/contexts/brainstorm.md +26 -0
- package/.agent/contexts/debug.md +28 -0
- package/.agent/contexts/implement.md +29 -0
- package/.agent/contexts/plan-quality-log.md +30 -0
- package/.agent/contexts/review.md +27 -0
- package/.agent/contexts/ship.md +28 -0
- package/.agent/decisions/001-trust-grade-governance.md +46 -0
- package/.agent/decisions/002-cross-ide-generation.md +15 -0
- package/.agent/engine/identity.json +4 -0
- package/.agent/engine/loading-rules.json +193 -0
- package/.agent/engine/marketplace-index.json +29 -0
- package/.agent/engine/mcp-servers/filesystem.json +9 -0
- package/.agent/engine/mcp-servers/github.json +11 -0
- package/.agent/engine/mcp-servers/postgres.json +11 -0
- package/.agent/engine/mcp-servers/supabase.json +11 -0
- package/.agent/engine/mcp-servers/vercel.json +11 -0
- package/.agent/engine/reliability-config.json +14 -0
- package/.agent/engine/sdlc-map.json +50 -0
- package/.agent/engine/workflow-state.json +167 -0
- package/.agent/hooks/README.md +101 -0
- package/.agent/hooks/hooks.json +104 -0
- package/.agent/hooks/templates/session-end.md +110 -0
- package/.agent/hooks/templates/session-start.md +95 -0
- package/.agent/manifest.json +466 -0
- package/.agent/rules/agent-upgrade-policy.md +56 -0
- package/.agent/rules/architecture.md +111 -0
- package/.agent/rules/coding-style.md +75 -0
- package/.agent/rules/documentation.md +74 -0
- package/.agent/rules/git-workflow.md +140 -0
- package/.agent/rules/quality-gate.md +117 -0
- package/.agent/rules/security.md +67 -0
- package/.agent/rules/sprint-tracking.md +103 -0
- package/.agent/rules/testing.md +80 -0
- package/.agent/rules/workflow-standards.md +30 -0
- package/.agent/rules.md +293 -0
- package/.agent/session-context.md +69 -0
- package/.agent/session-state.json +27 -0
- package/.agent/skills/README.md +135 -0
- package/.agent/skills/api-patterns/SKILL.md +117 -0
- package/.agent/skills/app-builder/SKILL.md +202 -0
- package/.agent/skills/architecture/SKILL.md +101 -0
- package/.agent/skills/behavioral-modes/SKILL.md +295 -0
- package/.agent/skills/brainstorming/SKILL.md +156 -0
- package/.agent/skills/clean-code/SKILL.md +142 -0
- package/.agent/skills/context-budget/SKILL.md +78 -0
- package/.agent/skills/continuous-learning/SKILL.md +145 -0
- package/.agent/skills/database-design/SKILL.md +303 -0
- package/.agent/skills/debugging-strategies/SKILL.md +158 -0
- package/.agent/skills/deployment-procedures/SKILL.md +191 -0
- package/.agent/skills/docker-patterns/SKILL.md +161 -0
- package/.agent/skills/eval-harness/SKILL.md +89 -0
- package/.agent/skills/frontend-patterns/SKILL.md +141 -0
- package/.agent/skills/git-workflow/SKILL.md +159 -0
- package/.agent/skills/i18n-localization/SKILL.md +191 -0
- package/.agent/skills/intelligent-routing/SKILL.md +180 -0
- package/.agent/skills/mcp-integration/SKILL.md +240 -0
- package/.agent/skills/mobile-design/SKILL.md +191 -0
- package/.agent/skills/nodejs-patterns/SKILL.md +164 -0
- package/.agent/skills/parallel-agents/SKILL.md +200 -0
- package/.agent/skills/performance-profiling/SKILL.md +134 -0
- package/.agent/skills/plan-validation/SKILL.md +192 -0
- package/.agent/skills/plan-writing/SKILL.md +183 -0
- package/.agent/skills/plan-writing/domain-enhancers.md +184 -0
- package/.agent/skills/plan-writing/plan-retrospective.md +116 -0
- package/.agent/skills/plan-writing/plan-schema.md +119 -0
- package/.agent/skills/pr-toolkit/SKILL.md +174 -0
- package/.agent/skills/production-readiness/SKILL.md +126 -0
- package/.agent/skills/security-practices/SKILL.md +109 -0
- package/.agent/skills/shell-conventions/SKILL.md +92 -0
- package/.agent/skills/strategic-compact/SKILL.md +62 -0
- package/.agent/skills/testing-patterns/SKILL.md +141 -0
- package/.agent/skills/typescript-expert/SKILL.md +160 -0
- package/.agent/skills/ui-ux-pro-max/SKILL.md +137 -0
- package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.agent/skills/verification-loop/SKILL.md +89 -0
- package/.agent/skills/webapp-testing/SKILL.md +175 -0
- package/.agent/templates/adr-template.md +32 -0
- package/.agent/templates/bug-report.md +37 -0
- package/.agent/templates/feature-request.md +32 -0
- package/.agent/workflows/README.md +101 -0
- package/.agent/workflows/brainstorm.md +86 -0
- package/.agent/workflows/create.md +85 -0
- package/.agent/workflows/debug.md +83 -0
- package/.agent/workflows/deploy.md +114 -0
- package/.agent/workflows/enhance.md +85 -0
- package/.agent/workflows/orchestrate.md +106 -0
- package/.agent/workflows/plan.md +105 -0
- package/.agent/workflows/pr-fix.md +163 -0
- package/.agent/workflows/pr-merge.md +117 -0
- package/.agent/workflows/pr-review.md +178 -0
- package/.agent/workflows/pr-split.md +118 -0
- package/.agent/workflows/pr.md +184 -0
- package/.agent/workflows/preflight.md +107 -0
- package/.agent/workflows/preview.md +95 -0
- package/.agent/workflows/quality-gate.md +103 -0
- package/.agent/workflows/retrospective.md +100 -0
- package/.agent/workflows/review.md +104 -0
- package/.agent/workflows/status.md +89 -0
- package/.agent/workflows/test.md +98 -0
- package/.agent/workflows/ui-ux-pro-max.md +93 -0
- package/.agent/workflows/upgrade.md +97 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/bin/kit.js +773 -0
- package/lib/agent-registry.js +228 -0
- package/lib/agent-reputation.js +343 -0
- package/lib/circuit-breaker.js +195 -0
- package/lib/cli-commands.js +322 -0
- package/lib/config-validator.js +274 -0
- package/lib/conflict-detector.js +252 -0
- package/lib/constants.js +47 -0
- package/lib/engineering-manager.js +336 -0
- package/lib/error-budget.js +370 -0
- package/lib/hook-system.js +256 -0
- package/lib/ide-generator.js +434 -0
- package/lib/identity.js +240 -0
- package/lib/io.js +146 -0
- package/lib/learning-engine.js +163 -0
- package/lib/loading-engine.js +421 -0
- package/lib/logger.js +118 -0
- package/lib/marketplace.js +321 -0
- package/lib/plugin-system.js +604 -0
- package/lib/plugin-verifier.js +197 -0
- package/lib/rate-limiter.js +113 -0
- package/lib/security-scanner.js +312 -0
- package/lib/self-healing.js +468 -0
- package/lib/session-manager.js +264 -0
- package/lib/skill-sandbox.js +244 -0
- package/lib/task-governance.js +522 -0
- package/lib/task-model.js +332 -0
- package/lib/updater.js +240 -0
- package/lib/verify.js +279 -0
- package/lib/workflow-engine.js +373 -0
- package/lib/workflow-events.js +166 -0
- package/lib/workflow-persistence.js +160 -0
- 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
|
+
};
|