proagents 1.0.4 → 1.0.6
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/lib/commands/init.js +248 -2
- package/package.json +3 -2
- /package/proagents/config/integrations/{github.yaml → github.template.yaml} +0 -0
- /package/proagents/config/integrations/{jira.yaml → jira.template.yaml} +0 -0
- /package/proagents/config/integrations/{linear.yaml → linear.template.yaml} +0 -0
- /package/proagents/config/integrations/{notion.yaml → notion.template.yaml} +0 -0
- /package/proagents/config/integrations/{slack.yaml → slack.template.yaml} +0 -0
package/lib/commands/init.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync, readdirSync
|
|
|
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);
|
|
@@ -9,11 +10,13 @@ const __dirname = dirname(__filename);
|
|
|
9
10
|
// Files/folders to preserve during update (user customizations)
|
|
10
11
|
const PRESERVE_PATHS = [
|
|
11
12
|
'active-features', // User's work in progress
|
|
12
|
-
'proagents.config.yaml', // User's configuration
|
|
13
13
|
'.learning', // Learned patterns
|
|
14
14
|
'cache', // Cached analysis
|
|
15
15
|
];
|
|
16
16
|
|
|
17
|
+
// Config file is handled specially - merged not preserved
|
|
18
|
+
const CONFIG_FILE = 'proagents.config.yaml';
|
|
19
|
+
|
|
17
20
|
// Files that should always be updated (framework files)
|
|
18
21
|
const FRAMEWORK_FOLDERS = [
|
|
19
22
|
'prompts',
|
|
@@ -109,7 +112,7 @@ export async function initCommand(options = {}) {
|
|
|
109
112
|
console.log(chalk.green('\n✓ ProAgents updated successfully!\n'));
|
|
110
113
|
console.log(chalk.gray('Preserved:'));
|
|
111
114
|
console.log(chalk.gray(' • active-features/ (your work in progress)'));
|
|
112
|
-
console.log(chalk.gray(' • proagents.config.yaml (your
|
|
115
|
+
console.log(chalk.gray(' • proagents.config.yaml (your values + new options merged)'));
|
|
113
116
|
console.log(chalk.gray(' • .learning/ (learned patterns)'));
|
|
114
117
|
console.log(chalk.gray(' • cache/ (analysis cache)\n'));
|
|
115
118
|
return;
|
|
@@ -284,6 +287,21 @@ async function smartUpdate(sourceDir, targetDir) {
|
|
|
284
287
|
}
|
|
285
288
|
}
|
|
286
289
|
|
|
290
|
+
// Before updating, migrate any modified template files
|
|
291
|
+
const migrationResult = migrateModifiedTemplates(sourceDir, targetDir);
|
|
292
|
+
if (migrationResult.migrated.length > 0) {
|
|
293
|
+
console.log(chalk.green(`✓ Migrated ${migrationResult.migrated.length} modified template(s) to user files`));
|
|
294
|
+
for (const file of migrationResult.migrated) {
|
|
295
|
+
console.log(chalk.gray(` • ${file}`));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (migrationResult.backedUp.length > 0) {
|
|
299
|
+
console.log(chalk.yellow(`⚠️ Backed up ${migrationResult.backedUp.length} modified template(s)`));
|
|
300
|
+
for (const file of migrationResult.backedUp) {
|
|
301
|
+
console.log(chalk.gray(` • ${file}`));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
287
305
|
// Update framework folders
|
|
288
306
|
let updatedCount = 0;
|
|
289
307
|
for (const folder of FRAMEWORK_FOLDERS) {
|
|
@@ -301,6 +319,9 @@ async function smartUpdate(sourceDir, targetDir) {
|
|
|
301
319
|
}
|
|
302
320
|
console.log(chalk.green(`✓ Updated ${updatedCount} framework folders`));
|
|
303
321
|
|
|
322
|
+
// Restore user files that were migrated (they were saved before folder deletion)
|
|
323
|
+
restoreUserFiles(targetDir, migrationResult);
|
|
324
|
+
|
|
304
325
|
// Update framework root files
|
|
305
326
|
let filesUpdated = 0;
|
|
306
327
|
for (const file of FRAMEWORK_FILES) {
|
|
@@ -314,6 +335,22 @@ async function smartUpdate(sourceDir, targetDir) {
|
|
|
314
335
|
}
|
|
315
336
|
console.log(chalk.green(`✓ Updated ${filesUpdated} framework files`));
|
|
316
337
|
|
|
338
|
+
// Merge config file - keep user values, add new options
|
|
339
|
+
const userConfigPath = join(targetDir, CONFIG_FILE);
|
|
340
|
+
const newConfigPath = join(sourceDir, CONFIG_FILE);
|
|
341
|
+
|
|
342
|
+
if (existsSync(newConfigPath)) {
|
|
343
|
+
const mergeResult = mergeConfigs(userConfigPath, newConfigPath);
|
|
344
|
+
if (mergeResult.newOptions > 0) {
|
|
345
|
+
console.log(chalk.green(`✓ Config merged: ${mergeResult.newOptions} new options added`));
|
|
346
|
+
if (mergeResult.newKeys.length > 0 && mergeResult.newKeys.length <= 5) {
|
|
347
|
+
console.log(chalk.gray(` New: ${mergeResult.newKeys.join(', ')}`));
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
console.log(chalk.green(`✓ Config up to date`));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
317
354
|
// Ensure preserved paths still exist (they should, but just in case)
|
|
318
355
|
for (const [path, fullPath] of Object.entries(backups)) {
|
|
319
356
|
if (!existsSync(fullPath)) {
|
|
@@ -327,3 +364,212 @@ async function smartUpdate(sourceDir, targetDir) {
|
|
|
327
364
|
}
|
|
328
365
|
}
|
|
329
366
|
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Deep merge two objects - source values override target, but target values are preserved
|
|
370
|
+
* Returns the merged object and list of new keys added
|
|
371
|
+
*/
|
|
372
|
+
function deepMerge(target, source, path = '') {
|
|
373
|
+
const result = { ...target };
|
|
374
|
+
const newKeys = [];
|
|
375
|
+
|
|
376
|
+
for (const key of Object.keys(source)) {
|
|
377
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
378
|
+
|
|
379
|
+
if (!(key in target)) {
|
|
380
|
+
// New key - add it
|
|
381
|
+
result[key] = source[key];
|
|
382
|
+
newKeys.push(currentPath);
|
|
383
|
+
} else if (
|
|
384
|
+
typeof source[key] === 'object' &&
|
|
385
|
+
source[key] !== null &&
|
|
386
|
+
!Array.isArray(source[key]) &&
|
|
387
|
+
typeof target[key] === 'object' &&
|
|
388
|
+
target[key] !== null &&
|
|
389
|
+
!Array.isArray(target[key])
|
|
390
|
+
) {
|
|
391
|
+
// Both are objects - recurse
|
|
392
|
+
const merged = deepMerge(target[key], source[key], currentPath);
|
|
393
|
+
result[key] = merged.result;
|
|
394
|
+
newKeys.push(...merged.newKeys);
|
|
395
|
+
}
|
|
396
|
+
// If key exists in target and is not an object, keep target value
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return { result, newKeys };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Merge user config with new framework config
|
|
404
|
+
* Preserves user values, adds new options from framework
|
|
405
|
+
*/
|
|
406
|
+
function mergeConfigs(userConfigPath, newConfigPath) {
|
|
407
|
+
try {
|
|
408
|
+
const newConfigContent = readFileSync(newConfigPath, 'utf-8');
|
|
409
|
+
const newConfig = yaml.load(newConfigContent);
|
|
410
|
+
|
|
411
|
+
if (!existsSync(userConfigPath)) {
|
|
412
|
+
// No user config - just copy the new one
|
|
413
|
+
writeFileSync(userConfigPath, newConfigContent);
|
|
414
|
+
return { newOptions: Object.keys(newConfig).length, newKeys: ['(new config created)'] };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const userConfigContent = readFileSync(userConfigPath, 'utf-8');
|
|
418
|
+
const userConfig = yaml.load(userConfigContent);
|
|
419
|
+
|
|
420
|
+
if (!userConfig || typeof userConfig !== 'object') {
|
|
421
|
+
// User config is empty or invalid - use new config
|
|
422
|
+
writeFileSync(userConfigPath, newConfigContent);
|
|
423
|
+
return { newOptions: Object.keys(newConfig).length, newKeys: ['(config replaced)'] };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Deep merge - user values take precedence, new keys added
|
|
427
|
+
const { result: mergedConfig, newKeys } = deepMerge(userConfig, newConfig);
|
|
428
|
+
|
|
429
|
+
if (newKeys.length > 0) {
|
|
430
|
+
// Write merged config with comment
|
|
431
|
+
const header = `# ProAgents Configuration\n# User values preserved, new options added during update\n# Last updated: ${new Date().toISOString().split('T')[0]}\n\n`;
|
|
432
|
+
const mergedYaml = yaml.dump(mergedConfig, {
|
|
433
|
+
indent: 2,
|
|
434
|
+
lineWidth: 120,
|
|
435
|
+
noRefs: true,
|
|
436
|
+
sortKeys: false
|
|
437
|
+
});
|
|
438
|
+
writeFileSync(userConfigPath, header + mergedYaml);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return { newOptions: newKeys.length, newKeys };
|
|
442
|
+
} catch (error) {
|
|
443
|
+
// If merge fails, don't break the update - just warn
|
|
444
|
+
console.log(chalk.yellow(`⚠️ Could not merge config: ${error.message}`));
|
|
445
|
+
return { newOptions: 0, newKeys: [] };
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Find all template files in a directory recursively
|
|
451
|
+
* Template files have '.template.' in their name
|
|
452
|
+
*/
|
|
453
|
+
function findTemplateFiles(dir, baseDir = dir) {
|
|
454
|
+
const templates = [];
|
|
455
|
+
|
|
456
|
+
if (!existsSync(dir)) return templates;
|
|
457
|
+
|
|
458
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
459
|
+
|
|
460
|
+
for (const entry of entries) {
|
|
461
|
+
const fullPath = join(dir, entry.name);
|
|
462
|
+
const relativePath = fullPath.replace(baseDir + '/', '');
|
|
463
|
+
|
|
464
|
+
if (entry.isDirectory()) {
|
|
465
|
+
templates.push(...findTemplateFiles(fullPath, baseDir));
|
|
466
|
+
} else if (entry.name.includes('.template.')) {
|
|
467
|
+
templates.push(relativePath);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return templates;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Get the user file path for a template file
|
|
476
|
+
* e.g., 'config/rules/custom-rules.template.yaml' -> 'config/rules/custom-rules.yaml'
|
|
477
|
+
*/
|
|
478
|
+
function getUserFilePath(templatePath) {
|
|
479
|
+
return templatePath.replace('.template.', '.');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Check if a file has been modified by comparing with source
|
|
484
|
+
*/
|
|
485
|
+
function isFileModified(sourcePath, targetPath) {
|
|
486
|
+
if (!existsSync(targetPath)) return false;
|
|
487
|
+
if (!existsSync(sourcePath)) return true; // File exists in target but not source = modified/custom
|
|
488
|
+
|
|
489
|
+
try {
|
|
490
|
+
const sourceContent = readFileSync(sourcePath, 'utf-8');
|
|
491
|
+
const targetContent = readFileSync(targetPath, 'utf-8');
|
|
492
|
+
return sourceContent !== targetContent;
|
|
493
|
+
} catch {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Migrate modified template files to user files before update
|
|
500
|
+
* Returns info about migrated and backed up files
|
|
501
|
+
*/
|
|
502
|
+
function migrateModifiedTemplates(sourceDir, targetDir) {
|
|
503
|
+
const result = {
|
|
504
|
+
migrated: [], // Templates copied to user files
|
|
505
|
+
backedUp: [], // Templates backed up (user file already existed)
|
|
506
|
+
userFiles: {} // Map of user file paths to their content (for restoration)
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// Find all template files in target directory
|
|
510
|
+
const templateFiles = findTemplateFiles(targetDir);
|
|
511
|
+
|
|
512
|
+
for (const templateRelPath of templateFiles) {
|
|
513
|
+
const templateTargetPath = join(targetDir, templateRelPath);
|
|
514
|
+
const templateSourcePath = join(sourceDir, templateRelPath);
|
|
515
|
+
const userFileRelPath = getUserFilePath(templateRelPath);
|
|
516
|
+
const userFilePath = join(targetDir, userFileRelPath);
|
|
517
|
+
|
|
518
|
+
// Check if template was modified by user
|
|
519
|
+
if (isFileModified(templateSourcePath, templateTargetPath)) {
|
|
520
|
+
try {
|
|
521
|
+
const modifiedContent = readFileSync(templateTargetPath, 'utf-8');
|
|
522
|
+
|
|
523
|
+
if (existsSync(userFilePath)) {
|
|
524
|
+
// User file already exists - backup the modified template
|
|
525
|
+
const backupPath = templateTargetPath + '.backup';
|
|
526
|
+
writeFileSync(backupPath, modifiedContent);
|
|
527
|
+
result.backedUp.push(templateRelPath);
|
|
528
|
+
// Save backup content for restoration after folder deletion
|
|
529
|
+
result.userFiles[templateRelPath + '.backup'] = modifiedContent;
|
|
530
|
+
} else {
|
|
531
|
+
// No user file - migrate template to user file
|
|
532
|
+
result.userFiles[userFileRelPath] = modifiedContent;
|
|
533
|
+
result.migrated.push(`${templateRelPath} → ${userFileRelPath}`);
|
|
534
|
+
}
|
|
535
|
+
} catch (error) {
|
|
536
|
+
// Skip files that can't be read
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Also preserve existing user files (non-template versions)
|
|
542
|
+
if (existsSync(userFilePath)) {
|
|
543
|
+
try {
|
|
544
|
+
const userContent = readFileSync(userFilePath, 'utf-8');
|
|
545
|
+
result.userFiles[userFileRelPath] = userContent;
|
|
546
|
+
} catch {
|
|
547
|
+
// Skip files that can't be read
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Restore user files after framework folders have been updated
|
|
557
|
+
*/
|
|
558
|
+
function restoreUserFiles(targetDir, migrationResult) {
|
|
559
|
+
for (const [relativePath, content] of Object.entries(migrationResult.userFiles)) {
|
|
560
|
+
const fullPath = join(targetDir, relativePath);
|
|
561
|
+
const dirPath = dirname(fullPath);
|
|
562
|
+
|
|
563
|
+
try {
|
|
564
|
+
// Ensure directory exists
|
|
565
|
+
if (!existsSync(dirPath)) {
|
|
566
|
+
mkdirSync(dirPath, { recursive: true });
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Write the user file
|
|
570
|
+
writeFileSync(fullPath, content);
|
|
571
|
+
} catch (error) {
|
|
572
|
+
console.log(chalk.yellow(`⚠️ Could not restore ${relativePath}: ${error.message}`));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proagents",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
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
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|