proagents 1.0.3 → 1.0.5

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 (2) hide show
  1. package/lib/commands/init.js +264 -10
  2. package/package.json +3 -2
@@ -1,11 +1,93 @@
1
- import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync } from 'fs';
1
+ import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync, readdirSync, rmSync } from 'fs';
2
2
  import { join, dirname } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import chalk from 'chalk';
5
+ import yaml from 'js-yaml';
5
6
 
6
7
  const __filename = fileURLToPath(import.meta.url);
7
8
  const __dirname = dirname(__filename);
8
9
 
10
+ // Files/folders to preserve during update (user customizations)
11
+ const PRESERVE_PATHS = [
12
+ 'active-features', // User's work in progress
13
+ '.learning', // Learned patterns
14
+ 'cache', // Cached analysis
15
+ ];
16
+
17
+ // Config file is handled specially - merged not preserved
18
+ const CONFIG_FILE = 'proagents.config.yaml';
19
+
20
+ // Files that should always be updated (framework files)
21
+ const FRAMEWORK_FOLDERS = [
22
+ 'prompts',
23
+ 'templates',
24
+ 'checklists',
25
+ 'standards',
26
+ 'examples',
27
+ 'git',
28
+ 'ui-integration',
29
+ 'workflow-modes',
30
+ 'security',
31
+ 'performance',
32
+ 'testing-standards',
33
+ 'patterns',
34
+ 'scaffolding',
35
+ 'cli',
36
+ 'adr',
37
+ 'ai-models',
38
+ 'ai-training',
39
+ 'api-versioning',
40
+ 'approval-workflows',
41
+ 'automation',
42
+ 'changelog',
43
+ 'cicd',
44
+ 'collaboration',
45
+ 'compliance',
46
+ 'config-versioning',
47
+ 'config',
48
+ 'contract-testing',
49
+ 'cost',
50
+ 'database',
51
+ 'dependency-management',
52
+ 'disaster-recovery',
53
+ 'environments',
54
+ 'existing-projects',
55
+ 'feature-flags',
56
+ 'getting-started',
57
+ 'i18n',
58
+ 'ide-integration',
59
+ 'integrations',
60
+ 'learning',
61
+ 'logging',
62
+ 'mcp',
63
+ 'metrics',
64
+ 'migrations',
65
+ 'monitoring',
66
+ 'multi-project',
67
+ 'notifications',
68
+ 'offline-mode',
69
+ 'parallel-features',
70
+ 'plugins',
71
+ 'pm-integration',
72
+ 'reporting',
73
+ 'reverse-engineering',
74
+ 'rules',
75
+ 'runbooks',
76
+ 'secrets',
77
+ 'team',
78
+ 'troubleshooting',
79
+ 'webhooks',
80
+ ];
81
+
82
+ // Root files to always update
83
+ const FRAMEWORK_FILES = [
84
+ 'README.md',
85
+ 'WORKFLOW.md',
86
+ 'PROAGENTS.md',
87
+ 'GETTING-STARTED-STORY.md',
88
+ 'slash-commands.json',
89
+ ];
90
+
9
91
  /**
10
92
  * Initialize ProAgents in the current project
11
93
  */
@@ -18,16 +100,37 @@ export async function initCommand(options = {}) {
18
100
  console.log(chalk.blue('========================\n'));
19
101
 
20
102
  // Check if already initialized
21
- if (existsSync(proagentsDir) && !options.force) {
22
- console.log(chalk.yellow('⚠️ ProAgents is already initialized in this project.'));
23
- console.log(chalk.gray(' Use --force to overwrite.\n'));
24
- return;
103
+ const alreadyInitialized = existsSync(proagentsDir);
104
+
105
+ if (alreadyInitialized && !options.force) {
106
+ // Smart update mode - preserve user files, update framework
107
+ console.log(chalk.cyan('ℹ️ ProAgents detected. Running smart update...'));
108
+ console.log(chalk.gray(' (Preserving your customizations)\n'));
109
+
110
+ try {
111
+ await smartUpdate(sourceDir, proagentsDir);
112
+ console.log(chalk.green('\n✓ ProAgents updated successfully!\n'));
113
+ console.log(chalk.gray('Preserved:'));
114
+ console.log(chalk.gray(' • active-features/ (your work in progress)'));
115
+ console.log(chalk.gray(' • proagents.config.yaml (your values + new options merged)'));
116
+ console.log(chalk.gray(' • .learning/ (learned patterns)'));
117
+ console.log(chalk.gray(' • cache/ (analysis cache)\n'));
118
+ return;
119
+ } catch (error) {
120
+ console.error(chalk.red('\n✗ Error updating ProAgents:'));
121
+ console.error(chalk.red(error.message));
122
+ process.exit(1);
123
+ }
25
124
  }
26
125
 
27
126
  try {
28
- // Copy proagents folder
127
+ if (options.force && alreadyInitialized) {
128
+ console.log(chalk.yellow('⚠️ Force mode: Overwriting all files...'));
129
+ }
130
+
131
+ // Fresh install or force overwrite
29
132
  console.log(chalk.gray('Copying framework files...'));
30
- cpSync(sourceDir, proagentsDir, { recursive: true, force: options.force || false });
133
+ cpSync(sourceDir, proagentsDir, { recursive: true, force: true });
31
134
  console.log(chalk.green('✓ Framework files copied to ./proagents/'));
32
135
 
33
136
  // Create config if not skipped
@@ -52,7 +155,6 @@ export async function initCommand(options = {}) {
52
155
  }
53
156
  if (!existsSync(releasesDir)) {
54
157
  mkdirSync(releasesDir, { recursive: true });
55
- // Create releases index
56
158
  writeFileSync(join(releasesDir, 'README.md'),
57
159
  `# Release History
58
160
 
@@ -143,12 +245,10 @@ For detailed commands, see \`./proagents/PROAGENTS.md\`
143
245
  if (existsSync(readmePath)) {
144
246
  const readmeContent = readFileSync(readmePath, 'utf-8');
145
247
  if (!readmeContent.includes('PROAGENTS:START')) {
146
- // Prepend to existing README
147
248
  writeFileSync(readmePath, proagentsSection + readmeContent);
148
249
  console.log(chalk.green('✓ Added ProAgents commands to README.md'));
149
250
  }
150
251
  } else {
151
- // Create new README with ProAgents section
152
252
  writeFileSync(readmePath, proagentsSection + `# Project Name\n\nProject description.\n`);
153
253
  console.log(chalk.green('✓ Created README.md with ProAgents commands'));
154
254
  }
@@ -173,3 +273,157 @@ For detailed commands, see \`./proagents/PROAGENTS.md\`
173
273
  process.exit(1);
174
274
  }
175
275
  }
276
+
277
+ /**
278
+ * Smart update - preserves user files, updates framework files
279
+ */
280
+ async function smartUpdate(sourceDir, targetDir) {
281
+ // Backup preserved paths
282
+ const backups = {};
283
+ for (const path of PRESERVE_PATHS) {
284
+ const fullPath = join(targetDir, path);
285
+ if (existsSync(fullPath)) {
286
+ backups[path] = fullPath;
287
+ }
288
+ }
289
+
290
+ // Update framework folders
291
+ let updatedCount = 0;
292
+ for (const folder of FRAMEWORK_FOLDERS) {
293
+ const sourcePath = join(sourceDir, folder);
294
+ const targetPath = join(targetDir, folder);
295
+
296
+ if (existsSync(sourcePath)) {
297
+ // Remove old folder and copy new
298
+ if (existsSync(targetPath)) {
299
+ rmSync(targetPath, { recursive: true, force: true });
300
+ }
301
+ cpSync(sourcePath, targetPath, { recursive: true });
302
+ updatedCount++;
303
+ }
304
+ }
305
+ console.log(chalk.green(`✓ Updated ${updatedCount} framework folders`));
306
+
307
+ // Update framework root files
308
+ let filesUpdated = 0;
309
+ for (const file of FRAMEWORK_FILES) {
310
+ const sourcePath = join(sourceDir, file);
311
+ const targetPath = join(targetDir, file);
312
+
313
+ if (existsSync(sourcePath)) {
314
+ cpSync(sourcePath, targetPath, { force: true });
315
+ filesUpdated++;
316
+ }
317
+ }
318
+ console.log(chalk.green(`✓ Updated ${filesUpdated} framework files`));
319
+
320
+ // Merge config file - keep user values, add new options
321
+ const userConfigPath = join(targetDir, CONFIG_FILE);
322
+ const newConfigPath = join(sourceDir, CONFIG_FILE);
323
+
324
+ if (existsSync(newConfigPath)) {
325
+ const mergeResult = mergeConfigs(userConfigPath, newConfigPath);
326
+ if (mergeResult.newOptions > 0) {
327
+ console.log(chalk.green(`✓ Config merged: ${mergeResult.newOptions} new options added`));
328
+ if (mergeResult.newKeys.length > 0 && mergeResult.newKeys.length <= 5) {
329
+ console.log(chalk.gray(` New: ${mergeResult.newKeys.join(', ')}`));
330
+ }
331
+ } else {
332
+ console.log(chalk.green(`✓ Config up to date`));
333
+ }
334
+ }
335
+
336
+ // Ensure preserved paths still exist (they should, but just in case)
337
+ for (const [path, fullPath] of Object.entries(backups)) {
338
+ if (!existsSync(fullPath)) {
339
+ console.log(chalk.yellow(`⚠️ ${path} was removed during update, restoring...`));
340
+ // The backup reference is the same as the original, so if it doesn't exist
341
+ // it means we need to recreate the structure
342
+ if (path === 'active-features') {
343
+ mkdirSync(fullPath, { recursive: true });
344
+ writeFileSync(join(fullPath, '.gitkeep'), '');
345
+ }
346
+ }
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Deep merge two objects - source values override target, but target values are preserved
352
+ * Returns the merged object and list of new keys added
353
+ */
354
+ function deepMerge(target, source, path = '') {
355
+ const result = { ...target };
356
+ const newKeys = [];
357
+
358
+ for (const key of Object.keys(source)) {
359
+ const currentPath = path ? `${path}.${key}` : key;
360
+
361
+ if (!(key in target)) {
362
+ // New key - add it
363
+ result[key] = source[key];
364
+ newKeys.push(currentPath);
365
+ } else if (
366
+ typeof source[key] === 'object' &&
367
+ source[key] !== null &&
368
+ !Array.isArray(source[key]) &&
369
+ typeof target[key] === 'object' &&
370
+ target[key] !== null &&
371
+ !Array.isArray(target[key])
372
+ ) {
373
+ // Both are objects - recurse
374
+ const merged = deepMerge(target[key], source[key], currentPath);
375
+ result[key] = merged.result;
376
+ newKeys.push(...merged.newKeys);
377
+ }
378
+ // If key exists in target and is not an object, keep target value
379
+ }
380
+
381
+ return { result, newKeys };
382
+ }
383
+
384
+ /**
385
+ * Merge user config with new framework config
386
+ * Preserves user values, adds new options from framework
387
+ */
388
+ function mergeConfigs(userConfigPath, newConfigPath) {
389
+ try {
390
+ const newConfigContent = readFileSync(newConfigPath, 'utf-8');
391
+ const newConfig = yaml.load(newConfigContent);
392
+
393
+ if (!existsSync(userConfigPath)) {
394
+ // No user config - just copy the new one
395
+ writeFileSync(userConfigPath, newConfigContent);
396
+ return { newOptions: Object.keys(newConfig).length, newKeys: ['(new config created)'] };
397
+ }
398
+
399
+ const userConfigContent = readFileSync(userConfigPath, 'utf-8');
400
+ const userConfig = yaml.load(userConfigContent);
401
+
402
+ if (!userConfig || typeof userConfig !== 'object') {
403
+ // User config is empty or invalid - use new config
404
+ writeFileSync(userConfigPath, newConfigContent);
405
+ return { newOptions: Object.keys(newConfig).length, newKeys: ['(config replaced)'] };
406
+ }
407
+
408
+ // Deep merge - user values take precedence, new keys added
409
+ const { result: mergedConfig, newKeys } = deepMerge(userConfig, newConfig);
410
+
411
+ if (newKeys.length > 0) {
412
+ // Write merged config with comment
413
+ const header = `# ProAgents Configuration\n# User values preserved, new options added during update\n# Last updated: ${new Date().toISOString().split('T')[0]}\n\n`;
414
+ const mergedYaml = yaml.dump(mergedConfig, {
415
+ indent: 2,
416
+ lineWidth: 120,
417
+ noRefs: true,
418
+ sortKeys: false
419
+ });
420
+ writeFileSync(userConfigPath, header + mergedYaml);
421
+ }
422
+
423
+ return { newOptions: newKeys.length, newKeys };
424
+ } catch (error) {
425
+ // If merge fails, don't break the update - just warn
426
+ console.log(chalk.yellow(`⚠️ Could not merge config: ${error.message}`));
427
+ return { newOptions: 0, newKeys: [] };
428
+ }
429
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "proagents",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "AI-agnostic development workflow framework that automates the full software development lifecycle",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -47,6 +47,7 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "commander": "^11.1.0",
50
- "chalk": "^5.3.0"
50
+ "chalk": "^5.3.0",
51
+ "js-yaml": "^4.1.0"
51
52
  }
52
53
  }