antigravity-ai-kit 2.1.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/.agent/README.md +4 -4
  2. package/.agent/agents/README.md +16 -12
  3. package/.agent/agents/architect.md +1 -0
  4. package/.agent/agents/backend-specialist.md +11 -0
  5. package/.agent/agents/code-reviewer.md +1 -0
  6. package/.agent/agents/database-architect.md +11 -0
  7. package/.agent/agents/devops-engineer.md +11 -0
  8. package/.agent/agents/e2e-runner.md +1 -0
  9. package/.agent/agents/explorer-agent.md +11 -0
  10. package/.agent/agents/frontend-specialist.md +11 -0
  11. package/.agent/agents/mobile-developer.md +11 -0
  12. package/.agent/agents/performance-optimizer.md +11 -0
  13. package/.agent/agents/planner.md +1 -0
  14. package/.agent/agents/refactor-cleaner.md +1 -0
  15. package/.agent/agents/reliability-engineer.md +11 -0
  16. package/.agent/agents/security-reviewer.md +1 -0
  17. package/.agent/agents/sprint-orchestrator.md +10 -0
  18. package/.agent/agents/tdd-guide.md +1 -0
  19. package/.agent/commands/code-review.md +1 -0
  20. package/.agent/commands/debug.md +1 -0
  21. package/.agent/commands/deploy.md +1 -0
  22. package/.agent/commands/help.md +252 -31
  23. package/.agent/commands/plan.md +1 -0
  24. package/.agent/commands/status.md +1 -0
  25. package/.agent/commands/tdd.md +1 -0
  26. package/.agent/contexts/brainstorm.md +26 -0
  27. package/.agent/contexts/debug.md +28 -0
  28. package/.agent/contexts/implement.md +29 -0
  29. package/.agent/contexts/review.md +27 -0
  30. package/.agent/contexts/ship.md +28 -0
  31. package/.agent/engine/identity.json +13 -0
  32. package/.agent/engine/loading-rules.json +23 -1
  33. package/.agent/engine/marketplace-index.json +29 -0
  34. package/.agent/engine/reliability-config.json +14 -0
  35. package/.agent/engine/sdlc-map.json +44 -0
  36. package/.agent/engine/workflow-state.json +28 -2
  37. package/.agent/hooks/hooks.json +27 -25
  38. package/.agent/manifest.json +12 -4
  39. package/.agent/rules.md +2 -1
  40. package/.agent/skills/README.md +10 -5
  41. package/.agent/skills/i18n-localization/SKILL.md +191 -0
  42. package/.agent/skills/mcp-integration/SKILL.md +224 -0
  43. package/.agent/skills/parallel-agents/SKILL.md +1 -1
  44. package/.agent/skills/shell-conventions/SKILL.md +92 -0
  45. package/.agent/skills/ui-ux-pro-max/SKILL.md +557 -0
  46. package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
  47. package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
  48. package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
  49. package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
  50. package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
  51. package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  52. package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  53. package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  54. package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  55. package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  56. package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  57. package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  58. package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  59. package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  60. package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  61. package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  62. package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  63. package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  64. package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  65. package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -0
  66. package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
  67. package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  68. package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  69. package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  70. package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -0
  71. package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  72. package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -0
  73. package/.agent/templates/adr-template.md +32 -0
  74. package/.agent/templates/bug-report.md +37 -0
  75. package/.agent/templates/feature-request.md +32 -0
  76. package/.agent/workflows/README.md +92 -78
  77. package/.agent/workflows/brainstorm.md +154 -100
  78. package/.agent/workflows/create.md +142 -75
  79. package/.agent/workflows/debug.md +157 -98
  80. package/.agent/workflows/deploy.md +195 -144
  81. package/.agent/workflows/enhance.md +157 -65
  82. package/.agent/workflows/orchestrate.md +171 -114
  83. package/.agent/workflows/plan.md +147 -72
  84. package/.agent/workflows/preview.md +140 -83
  85. package/.agent/workflows/quality-gate.md +196 -0
  86. package/.agent/workflows/retrospective.md +197 -0
  87. package/.agent/workflows/review.md +188 -0
  88. package/.agent/workflows/status.md +142 -91
  89. package/.agent/workflows/test.md +168 -95
  90. package/.agent/workflows/ui-ux-pro-max.md +181 -127
  91. package/README.md +215 -78
  92. package/bin/ag-kit.js +344 -10
  93. package/lib/agent-registry.js +214 -0
  94. package/lib/agent-reputation.js +351 -0
  95. package/lib/cli-commands.js +235 -0
  96. package/lib/conflict-detector.js +245 -0
  97. package/lib/engineering-manager.js +354 -0
  98. package/lib/error-budget.js +294 -0
  99. package/lib/hook-system.js +252 -0
  100. package/lib/identity.js +245 -0
  101. package/lib/loading-engine.js +208 -0
  102. package/lib/marketplace.js +298 -0
  103. package/lib/plugin-system.js +604 -0
  104. package/lib/security-scanner.js +309 -0
  105. package/lib/self-healing.js +434 -0
  106. package/lib/session-manager.js +261 -0
  107. package/lib/skill-sandbox.js +244 -0
  108. package/lib/task-governance.js +523 -0
  109. package/lib/task-model.js +317 -0
  110. package/lib/updater.js +201 -0
  111. package/lib/verify.js +240 -0
  112. package/lib/workflow-engine.js +353 -0
  113. package/lib/workflow-persistence.js +160 -0
  114. package/package.json +7 -3
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Antigravity 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 = '.agent';
19
+ const ENGINE_DIR = 'engine';
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
+ const tempPath = `${tasksPath}.tmp`;
92
+ const dir = path.dirname(tasksPath);
93
+
94
+ if (!fs.existsSync(dir)) {
95
+ fs.mkdirSync(dir, { recursive: true });
96
+ }
97
+
98
+ const data = {
99
+ schemaVersion: '1.0.0',
100
+ lastUpdated: new Date().toISOString(),
101
+ tasks,
102
+ };
103
+
104
+ fs.writeFileSync(tempPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
105
+ fs.renameSync(tempPath, tasksPath);
106
+ }
107
+
108
+ /**
109
+ * Creates a new task.
110
+ *
111
+ * @param {string} projectRoot - Root directory of the project
112
+ * @param {object} params - Task parameters
113
+ * @param {string} params.title - Task title
114
+ * @param {string} [params.description] - Optional description
115
+ * @param {string} [params.assignee] - Optional assignee
116
+ * @param {string} [params.priority] - Priority (default: 'medium')
117
+ * @returns {Task}
118
+ */
119
+ function createTask(projectRoot, { title, description, assignee, priority }) {
120
+ const tasks = loadTasks(projectRoot);
121
+ const now = new Date().toISOString();
122
+
123
+ const taskPriority = priority && VALID_PRIORITIES.includes(priority) ? priority : 'medium';
124
+
125
+ /** @type {Task} */
126
+ const task = {
127
+ id: `TSK-${crypto.randomUUID().slice(0, 8).toUpperCase()}`,
128
+ title,
129
+ description: description || null,
130
+ status: 'open',
131
+ priority: taskPriority,
132
+ assignee: assignee || null,
133
+ createdAt: now,
134
+ updatedAt: now,
135
+ completedAt: null,
136
+ deleted: false,
137
+ };
138
+
139
+ tasks.push(task);
140
+ writeTasks(projectRoot, tasks);
141
+
142
+ return task;
143
+ }
144
+
145
+ /**
146
+ * Retrieves a task by ID.
147
+ *
148
+ * @param {string} projectRoot - Root directory of the project
149
+ * @param {string} taskId - Task ID
150
+ * @returns {Task | null}
151
+ */
152
+ function getTask(projectRoot, taskId) {
153
+ const tasks = loadTasks(projectRoot);
154
+ return tasks.find((t) => t.id === taskId && !t.deleted) || null;
155
+ }
156
+
157
+ /**
158
+ * Lists tasks with optional filters.
159
+ *
160
+ * @param {string} projectRoot - Root directory of the project
161
+ * @param {object} [filters] - Filter options
162
+ * @param {string} [filters.status] - Filter by status
163
+ * @param {string} [filters.priority] - Filter by priority
164
+ * @param {string} [filters.assignee] - Filter by assignee
165
+ * @returns {Task[]}
166
+ */
167
+ function listTasks(projectRoot, filters = {}) {
168
+ let tasks = loadTasks(projectRoot).filter((t) => !t.deleted);
169
+
170
+ if (filters.status) {
171
+ tasks = tasks.filter((t) => t.status === filters.status);
172
+ }
173
+ if (filters.priority) {
174
+ tasks = tasks.filter((t) => t.priority === filters.priority);
175
+ }
176
+ if (filters.assignee) {
177
+ tasks = tasks.filter((t) => t.assignee === filters.assignee);
178
+ }
179
+
180
+ return tasks;
181
+ }
182
+
183
+ /**
184
+ * Updates a task's fields.
185
+ *
186
+ * @param {string} projectRoot - Root directory of the project
187
+ * @param {string} taskId - Task ID
188
+ * @param {object} updates - Fields to update
189
+ * @returns {{ success: boolean, task: Task | null }}
190
+ */
191
+ function updateTask(projectRoot, taskId, updates) {
192
+ const tasks = loadTasks(projectRoot);
193
+ const taskIndex = tasks.findIndex((t) => t.id === taskId && !t.deleted);
194
+
195
+ if (taskIndex === -1) {
196
+ return { success: false, task: null };
197
+ }
198
+
199
+ const allowedFields = ['title', 'description', 'assignee', 'priority'];
200
+ for (const field of allowedFields) {
201
+ if (updates[field] !== undefined) {
202
+ tasks[taskIndex][field] = updates[field];
203
+ }
204
+ }
205
+
206
+ tasks[taskIndex].updatedAt = new Date().toISOString();
207
+ writeTasks(projectRoot, tasks);
208
+
209
+ return { success: true, task: tasks[taskIndex] };
210
+ }
211
+
212
+ /**
213
+ * Transitions a task to a new status following the FSM.
214
+ *
215
+ * @param {string} projectRoot - Root directory of the project
216
+ * @param {string} taskId - Task ID
217
+ * @param {string} newStatus - Target status
218
+ * @returns {{ success: boolean, error?: string }}
219
+ */
220
+ function transitionTask(projectRoot, taskId, newStatus) {
221
+ if (!VALID_STATUSES.includes(newStatus)) {
222
+ return { success: false, error: `Invalid status: ${newStatus}. Valid: ${VALID_STATUSES.join(', ')}` };
223
+ }
224
+
225
+ const tasks = loadTasks(projectRoot);
226
+ const taskIndex = tasks.findIndex((t) => t.id === taskId && !t.deleted);
227
+
228
+ if (taskIndex === -1) {
229
+ return { success: false, error: `Task not found: ${taskId}` };
230
+ }
231
+
232
+ const currentStatus = tasks[taskIndex].status;
233
+ const allowed = STATUS_TRANSITIONS[currentStatus] || [];
234
+
235
+ if (!allowed.includes(newStatus)) {
236
+ return {
237
+ success: false,
238
+ error: `Invalid transition: ${currentStatus} → ${newStatus}. Allowed: [${allowed.join(', ')}]`,
239
+ };
240
+ }
241
+
242
+ tasks[taskIndex].status = newStatus;
243
+ tasks[taskIndex].updatedAt = new Date().toISOString();
244
+
245
+ if (newStatus === 'done') {
246
+ tasks[taskIndex].completedAt = new Date().toISOString();
247
+ }
248
+
249
+ writeTasks(projectRoot, tasks);
250
+
251
+ return { success: true };
252
+ }
253
+
254
+ /**
255
+ * Soft-deletes a task.
256
+ *
257
+ * @param {string} projectRoot - Root directory of the project
258
+ * @param {string} taskId - Task ID
259
+ * @returns {{ success: boolean }}
260
+ */
261
+ function deleteTask(projectRoot, taskId) {
262
+ const tasks = loadTasks(projectRoot);
263
+ const taskIndex = tasks.findIndex((t) => t.id === taskId);
264
+
265
+ if (taskIndex === -1) {
266
+ return { success: false };
267
+ }
268
+
269
+ tasks[taskIndex].deleted = true;
270
+ tasks[taskIndex].updatedAt = new Date().toISOString();
271
+ writeTasks(projectRoot, tasks);
272
+
273
+ return { success: true };
274
+ }
275
+
276
+ /**
277
+ * Returns task metrics: counts by status, avg cycle time.
278
+ *
279
+ * @param {string} projectRoot - Root directory of the project
280
+ * @returns {object} Task metrics
281
+ */
282
+ function getTaskMetrics(projectRoot) {
283
+ const tasks = loadTasks(projectRoot).filter((t) => !t.deleted);
284
+
285
+ const counts = {};
286
+ for (const status of VALID_STATUSES) {
287
+ counts[status] = tasks.filter((t) => t.status === status).length;
288
+ }
289
+
290
+ // Calculate average cycle time for completed tasks
291
+ const completedTasks = tasks.filter((t) => t.status === 'done' && t.completedAt);
292
+ let avgCycleTimeSeconds = 0;
293
+
294
+ if (completedTasks.length > 0) {
295
+ const totalCycleTime = completedTasks.reduce((sum, t) => {
296
+ return sum + (new Date(t.completedAt).getTime() - new Date(t.createdAt).getTime());
297
+ }, 0);
298
+ avgCycleTimeSeconds = Math.round(totalCycleTime / completedTasks.length / 1000);
299
+ }
300
+
301
+ return {
302
+ total: tasks.length,
303
+ counts,
304
+ avgCycleTimeSeconds,
305
+ completionRate: tasks.length > 0 ? Math.round((counts.done / tasks.length) * 100) : 0,
306
+ };
307
+ }
308
+
309
+ module.exports = {
310
+ createTask,
311
+ getTask,
312
+ listTasks,
313
+ updateTask,
314
+ transitionTask,
315
+ deleteTask,
316
+ getTaskMetrics,
317
+ };
package/lib/updater.js ADDED
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Antigravity 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 = '.agent';
20
+
21
+ /** Files that should never be overwritten during updates */
22
+ const PRESERVED_FILES = new Set([
23
+ 'session-context.md',
24
+ 'session-state.json',
25
+ ]);
26
+
27
+ /** Directories whose contents should never be overwritten */
28
+ const PRESERVED_DIRS = new Set([
29
+ 'decisions',
30
+ ]);
31
+
32
+ /**
33
+ * @typedef {object} UpdateReport
34
+ * @property {string[]} added - Files that were added (new in source)
35
+ * @property {string[]} updated - Files that were updated (hash mismatch)
36
+ * @property {string[]} skipped - Files that were preserved (user customizations)
37
+ * @property {string[]} unchanged - Files that are identical
38
+ */
39
+
40
+ /**
41
+ * Computes a SHA-256 hash of a file's contents.
42
+ *
43
+ * @param {string} filePath - Absolute path to the file
44
+ * @returns {string} Hex-encoded SHA-256 hash
45
+ */
46
+ function fileHash(filePath) {
47
+ const content = fs.readFileSync(filePath);
48
+ return crypto.createHash('sha256').update(content).digest('hex');
49
+ }
50
+
51
+ /**
52
+ * Recursively collects all file paths relative to a root directory.
53
+ *
54
+ * @param {string} rootDir - Root directory to scan
55
+ * @param {string} [prefix=''] - Path prefix for recursion
56
+ * @returns {string[]} Array of relative paths
57
+ */
58
+ function collectFiles(rootDir, prefix = '') {
59
+ /** @type {string[]} */
60
+ const files = [];
61
+
62
+ if (!fs.existsSync(rootDir)) {
63
+ return files;
64
+ }
65
+
66
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
67
+
68
+ for (const entry of entries) {
69
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
70
+
71
+ if (entry.isDirectory()) {
72
+ files.push(...collectFiles(path.join(rootDir, entry.name), relativePath));
73
+ } else {
74
+ files.push(relativePath);
75
+ }
76
+ }
77
+
78
+ return files;
79
+ }
80
+
81
+ /**
82
+ * Determines if a relative file path is in the preserved set.
83
+ *
84
+ * @param {string} relativePath - Relative path from .agent/ root
85
+ * @returns {boolean} True if the file should be preserved
86
+ */
87
+ function isPreservedFile(relativePath) {
88
+ // Check exact filename matches
89
+ const basename = path.basename(relativePath);
90
+ if (PRESERVED_FILES.has(basename)) {
91
+ return true;
92
+ }
93
+
94
+ // Check if inside a preserved directory
95
+ const parts = relativePath.split(/[/\\]/);
96
+ for (const dir of PRESERVED_DIRS) {
97
+ if (parts.includes(dir)) {
98
+ return true;
99
+ }
100
+ }
101
+
102
+ return false;
103
+ }
104
+
105
+ /**
106
+ * Generates a diff report comparing source and target .agent/ directories.
107
+ *
108
+ * @param {string} sourceRoot - Root of the package (source .agent/ location)
109
+ * @param {string} targetRoot - Root of the project (target .agent/ location)
110
+ * @returns {UpdateReport}
111
+ */
112
+ function generateDiff(sourceRoot, targetRoot) {
113
+ const sourceDir = path.join(sourceRoot, AGENT_DIR);
114
+ const targetDir = path.join(targetRoot, AGENT_DIR);
115
+
116
+ const sourceFiles = collectFiles(sourceDir);
117
+ const targetFiles = new Set(collectFiles(targetDir));
118
+
119
+ /** @type {UpdateReport} */
120
+ const report = {
121
+ added: [],
122
+ updated: [],
123
+ skipped: [],
124
+ unchanged: [],
125
+ };
126
+
127
+ for (const relativeFile of sourceFiles) {
128
+ const sourcePath = path.join(sourceDir, relativeFile);
129
+ const targetPath = path.join(targetDir, relativeFile);
130
+
131
+ if (isPreservedFile(relativeFile) && targetFiles.has(relativeFile)) {
132
+ report.skipped.push(relativeFile);
133
+ continue;
134
+ }
135
+
136
+ if (!targetFiles.has(relativeFile)) {
137
+ report.added.push(relativeFile);
138
+ continue;
139
+ }
140
+
141
+ const sourceHash = fileHash(sourcePath);
142
+ const targetHash = fileHash(targetPath);
143
+
144
+ if (sourceHash !== targetHash) {
145
+ report.updated.push(relativeFile);
146
+ } else {
147
+ report.unchanged.push(relativeFile);
148
+ }
149
+ }
150
+
151
+ return report;
152
+ }
153
+
154
+ /**
155
+ * Applies a non-destructive update from source to target.
156
+ *
157
+ * @param {string} sourceRoot - Root of the package
158
+ * @param {string} targetRoot - Root of the project
159
+ * @param {boolean} [dryRun=false] - If true, report only without modifying
160
+ * @returns {UpdateReport}
161
+ */
162
+ function applyUpdate(sourceRoot, targetRoot, dryRun = false) {
163
+ const report = generateDiff(sourceRoot, targetRoot);
164
+
165
+ if (dryRun) {
166
+ return report;
167
+ }
168
+
169
+ const sourceDir = path.join(sourceRoot, AGENT_DIR);
170
+ const targetDir = path.join(targetRoot, AGENT_DIR);
171
+
172
+ // Copy new files
173
+ for (const relativeFile of report.added) {
174
+ const sourcePath = path.join(sourceDir, relativeFile);
175
+ const targetPath = path.join(targetDir, relativeFile);
176
+ const targetDirPath = path.dirname(targetPath);
177
+
178
+ if (!fs.existsSync(targetDirPath)) {
179
+ fs.mkdirSync(targetDirPath, { recursive: true });
180
+ }
181
+
182
+ fs.copyFileSync(sourcePath, targetPath);
183
+ }
184
+
185
+ // Update modified files
186
+ for (const relativeFile of report.updated) {
187
+ const sourcePath = path.join(sourceDir, relativeFile);
188
+ const targetPath = path.join(targetDir, relativeFile);
189
+
190
+ fs.copyFileSync(sourcePath, targetPath);
191
+ }
192
+
193
+ return report;
194
+ }
195
+
196
+ module.exports = {
197
+ generateDiff,
198
+ applyUpdate,
199
+ isPreservedFile,
200
+ collectFiles,
201
+ };