elsabro 7.3.0 → 7.3.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.
@@ -371,4 +371,258 @@ describe('skill-install.sh', () => {
371
371
  assert.ok(['ok', 'error'].includes(errResult.status));
372
372
  });
373
373
  });
374
+
375
+ // ---- Auto-install integration ----
376
+ describe('Auto-install integration', () => {
377
+ it('completes full workflow: check → install → validate', () => {
378
+ const mockDir = createMockNpx('exit 0');
379
+ const mockHome = createMockSkillsDir({
380
+ 'integration-test.md': '---\nname: integration-test\n---\n# Test skill'
381
+ });
382
+ try {
383
+ // Step 1: Check registry availability
384
+ const checkResult = runInstall('check', {
385
+ env: { PATH: `${mockDir}:${process.env.PATH}` }
386
+ });
387
+ assert.equal(checkResult.status, 'ok', 'Registry check should succeed');
388
+
389
+ // Step 2: Install skill
390
+ const installResult = runInstall(
391
+ 'install "npx -y skills add vercel-labs/agent-skills --skill integration-test -g -a claude-code"',
392
+ { env: { PATH: `${mockDir}:${process.env.PATH}`, HOME: mockHome } }
393
+ );
394
+ assert.equal(installResult.status, 'ok', 'Install should succeed');
395
+ assert.equal(installResult.skill, 'integration-test', 'Should extract correct skill name');
396
+
397
+ // Step 3: Validate installed skill
398
+ const validateResult = runInstall('validate "integration-test"', {
399
+ env: { HOME: mockHome }
400
+ });
401
+ assert.equal(validateResult.status, 'ok', 'Validation should succeed');
402
+ assert.equal(validateResult.has_frontmatter, true, 'Should detect frontmatter');
403
+ } finally {
404
+ cleanup(mockDir);
405
+ cleanup(mockHome);
406
+ }
407
+ });
408
+
409
+ it('handles install failure gracefully in workflow', () => {
410
+ const mockDir = createMockNpx('echo "Network error" >&2; exit 1');
411
+ const mockHome = createMockSkillsDir();
412
+ try {
413
+ // Step 1: Check succeeds (mock npx returns 0 for some calls)
414
+ const mockDirCheck = createMockNpx('exit 0');
415
+ const checkResult = runInstall('check', {
416
+ env: { PATH: `${mockDirCheck}:${process.env.PATH}` }
417
+ });
418
+ cleanup(mockDirCheck);
419
+ assert.equal(checkResult.status, 'ok');
420
+
421
+ // Step 2: Install fails
422
+ const installResult = runInstall(
423
+ 'install "npx -y skills add vercel-labs/agent-skills --skill bad-network -g -a claude-code"',
424
+ { env: { PATH: `${mockDir}:${process.env.PATH}`, HOME: mockHome } }
425
+ );
426
+ assert.equal(installResult.status, 'error', 'Install should fail on network error');
427
+ assert.ok(installResult.message.includes('install failed'), 'Error message should indicate install failure');
428
+
429
+ // Step 3: Validation fails (skill not installed)
430
+ const validateResult = runInstall('validate "bad-network"', {
431
+ env: { HOME: mockHome }
432
+ });
433
+ assert.equal(validateResult.status, 'error', 'Validation should fail for non-existent skill');
434
+ } finally {
435
+ cleanup(mockDir);
436
+ cleanup(mockHome);
437
+ }
438
+ });
439
+
440
+ it('detects missing skill and triggers install suggestion', () => {
441
+ const mockHome = createMockSkillsDir({
442
+ 'existing-skill.md': '---\nname: existing-skill\n---\n# Exists'
443
+ });
444
+ try {
445
+ // Try to validate a skill that doesn't exist
446
+ const validateResult = runInstall('validate "missing-skill"', {
447
+ env: { HOME: mockHome }
448
+ });
449
+ assert.equal(validateResult.status, 'error', 'Should error on missing skill');
450
+ assert.ok(validateResult.message.includes('not found'), 'Error should indicate skill not found');
451
+ } finally {
452
+ cleanup(mockHome);
453
+ }
454
+ });
455
+
456
+ it('verifies skill availability after successful install', () => {
457
+ const mockDir = createMockNpx('exit 0');
458
+ const mockHome = createMockSkillsDir();
459
+ try {
460
+ // Install skill
461
+ const installResult = runInstall(
462
+ 'install "npx -y skills add vercel-labs/agent-skills --skill verify-test -g -a claude-code"',
463
+ { env: { PATH: `${mockDir}:${process.env.PATH}`, HOME: mockHome } }
464
+ );
465
+ assert.equal(installResult.status, 'ok');
466
+
467
+ // Create the skill file to simulate successful installation
468
+ const skillPath = path.join(mockHome, '.claude', 'skills', 'verify-test.md');
469
+ fs.writeFileSync(skillPath, '---\nname: verify-test\n---\n# Installed');
470
+
471
+ // Verify skill is now available
472
+ const validateResult = runInstall('validate "verify-test"', {
473
+ env: { HOME: mockHome }
474
+ });
475
+ assert.equal(validateResult.status, 'ok', 'Skill should be available after install');
476
+ assert.equal(validateResult.has_frontmatter, true);
477
+ } finally {
478
+ cleanup(mockDir);
479
+ cleanup(mockHome);
480
+ }
481
+ });
482
+
483
+ it('handles concurrent install attempts gracefully', () => {
484
+ const mockDir = createMockNpx('sleep 0.1; exit 0');
485
+ const mockHome = createMockSkillsDir();
486
+ try {
487
+ // Simulate checking multiple skills in sequence (not parallel, per spec)
488
+ const skills = ['skill-a', 'skill-b', 'skill-c'];
489
+ const results = skills.map(skill =>
490
+ runInstall(
491
+ `install "npx -y skills add vercel-labs/agent-skills --skill ${skill} -g -a claude-code"`,
492
+ { env: { PATH: `${mockDir}:${process.env.PATH}`, HOME: mockHome } }
493
+ )
494
+ );
495
+
496
+ // All should succeed
497
+ results.forEach((result, idx) => {
498
+ assert.equal(result.status, 'ok', `Install ${idx + 1} should succeed`);
499
+ assert.equal(result.skill, skills[idx], `Should extract correct skill name ${skills[idx]}`);
500
+ });
501
+ } finally {
502
+ cleanup(mockDir);
503
+ cleanup(mockHome);
504
+ }
505
+ });
506
+
507
+ it('recovers from partial install failure', () => {
508
+ const mockDir = createMockNpx('exit 0');
509
+ const mockHome = createMockSkillsDir();
510
+ try {
511
+ // First install succeeds
512
+ const install1 = runInstall(
513
+ 'install "npx -y skills add vercel-labs/agent-skills --skill partial-1 -g -a claude-code"',
514
+ { env: { PATH: `${mockDir}:${process.env.PATH}`, HOME: mockHome } }
515
+ );
516
+ assert.equal(install1.status, 'ok');
517
+
518
+ // Second install with failing npx
519
+ const mockDirFail = createMockNpx('exit 1');
520
+ const install2 = runInstall(
521
+ 'install "npx -y skills add vercel-labs/agent-skills --skill partial-2 -g -a claude-code"',
522
+ { env: { PATH: `${mockDirFail}:${process.env.PATH}`, HOME: mockHome } }
523
+ );
524
+ cleanup(mockDirFail);
525
+ assert.equal(install2.status, 'error', 'Second install should fail');
526
+
527
+ // Third install succeeds (recovery)
528
+ const install3 = runInstall(
529
+ 'install "npx -y skills add vercel-labs/agent-skills --skill partial-3 -g -a claude-code"',
530
+ { env: { PATH: `${mockDir}:${process.env.PATH}`, HOME: mockHome } }
531
+ );
532
+ assert.equal(install3.status, 'ok', 'Should recover after failure');
533
+ } finally {
534
+ cleanup(mockDir);
535
+ cleanup(mockHome);
536
+ }
537
+ });
538
+
539
+ it('validates skill format after auto-install', () => {
540
+ const mockDir = createMockNpx('exit 0');
541
+ const mockHome = createMockSkillsDir();
542
+ try {
543
+ // Install skill
544
+ runInstall(
545
+ 'install "npx -y skills add vercel-labs/agent-skills --skill format-test -g -a claude-code"',
546
+ { env: { PATH: `${mockDir}:${process.env.PATH}`, HOME: mockHome } }
547
+ );
548
+
549
+ // Create skill with invalid format (no frontmatter)
550
+ const skillPath = path.join(mockHome, '.claude', 'skills', 'format-test.md');
551
+ fs.writeFileSync(skillPath, '# No frontmatter\nJust content');
552
+
553
+ // Validate should still succeed but report no frontmatter
554
+ const validateResult = runInstall('validate "format-test"', {
555
+ env: { HOME: mockHome }
556
+ });
557
+ assert.equal(validateResult.status, 'ok', 'Should succeed even without frontmatter');
558
+ assert.equal(validateResult.has_frontmatter, false, 'Should detect missing frontmatter');
559
+ } finally {
560
+ cleanup(mockDir);
561
+ cleanup(mockHome);
562
+ }
563
+ });
564
+
565
+ it('handles registry unavailable during auto-install', () => {
566
+ const mockDir = createMockNpx('echo "Registry unavailable" >&2; exit 1');
567
+ try {
568
+ const checkResult = runInstall('check', {
569
+ env: { PATH: `${mockDir}:${process.env.PATH}` }
570
+ });
571
+ assert.equal(checkResult.status, 'error', 'Should fail when registry unavailable');
572
+ assert.ok(checkResult.message.includes('registry check failed'), 'Should indicate registry failure');
573
+ } finally {
574
+ cleanup(mockDir);
575
+ }
576
+ });
577
+
578
+ it('provides clear error for malformed install commands', () => {
579
+ const invalidCommands = [
580
+ '', // Empty
581
+ 'not-npx-command', // Wrong prefix
582
+ 'npx skills add', // Missing --skill flag
583
+ ];
584
+
585
+ for (const cmd of invalidCommands) {
586
+ const result = runInstall(`install "${cmd}"`);
587
+ assert.equal(result.status, 'error', `Should error for invalid command: ${cmd}`);
588
+ assert.ok(result.message, 'Should provide error message');
589
+ }
590
+ });
591
+
592
+ it('maintains cache consistency across install workflow', () => {
593
+ const mockDir = createMockNpx('exit 0');
594
+ const mockHome = createMockSkillsDir();
595
+ const cacheDir = path.join(PROJECT_ROOT, '.cache');
596
+ const cacheFile = path.join(cacheDir, 'skill-discovery-cache.json');
597
+
598
+ // Save original cache state
599
+ const originalContent = fs.existsSync(cacheFile)
600
+ ? fs.readFileSync(cacheFile, 'utf8')
601
+ : null;
602
+
603
+ try {
604
+ // Create cache
605
+ if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
606
+ fs.writeFileSync(cacheFile, '{"cached":"before-install"}');
607
+ assert.equal(fs.existsSync(cacheFile), true, 'Cache should exist before install');
608
+
609
+ // Install should invalidate cache
610
+ runInstall(
611
+ 'install "npx -y skills add vercel-labs/agent-skills --skill cache-workflow -g -a claude-code"',
612
+ { env: { PATH: `${mockDir}:${process.env.PATH}`, HOME: mockHome } }
613
+ );
614
+
615
+ assert.equal(fs.existsSync(cacheFile), false, 'Cache should be invalidated after install');
616
+ } finally {
617
+ cleanup(mockDir);
618
+ cleanup(mockHome);
619
+ // Restore original cache state
620
+ if (originalContent !== null) {
621
+ fs.writeFileSync(cacheFile, originalContent);
622
+ } else if (fs.existsSync(cacheFile)) {
623
+ fs.unlinkSync(cacheFile);
624
+ }
625
+ }
626
+ });
627
+ });
374
628
  });
@@ -0,0 +1,137 @@
1
+ 'use strict';
2
+
3
+ const { describe, it } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const { resolveExpression } = require('../src/template');
6
+
7
+ describe('hasCriticalIssues validation', () => {
8
+ it('detects "critical" pattern', () => {
9
+ const ctx = {
10
+ review: {
11
+ issues: [{ severity: 'critical', msg: 'bug found' }]
12
+ }
13
+ };
14
+ const result = resolveExpression('hasCriticalIssues(review)', ctx);
15
+ assert.equal(result, true);
16
+ });
17
+
18
+ it('detects "blocking" pattern', () => {
19
+ const ctx = {
20
+ review: {
21
+ issues: [{ priority: 'blocking', msg: 'deployment blocked' }]
22
+ }
23
+ };
24
+ const result = resolveExpression('hasCriticalIssues(review)', ctx);
25
+ assert.equal(result, true);
26
+ });
27
+
28
+ it('detects "P0" pattern', () => {
29
+ const ctx = {
30
+ review: {
31
+ issues: [{ priority: 'P0', msg: 'critical bug' }]
32
+ }
33
+ };
34
+ const result = resolveExpression('hasCriticalIssues(review)', ctx);
35
+ assert.equal(result, true);
36
+ });
37
+
38
+ it('detects "MUST_FIX" pattern', () => {
39
+ const ctx = {
40
+ review: {
41
+ issues: [{ status: 'MUST_FIX', msg: 'security vulnerability' }]
42
+ }
43
+ };
44
+ const result = resolveExpression('hasCriticalIssues(review)', ctx);
45
+ assert.equal(result, true);
46
+ });
47
+
48
+ it('detects "URGENT" pattern', () => {
49
+ const ctx = {
50
+ review: {
51
+ issues: [{ severity: 'URGENT', msg: 'production down' }]
52
+ }
53
+ };
54
+ const result = resolveExpression('hasCriticalIssues(review)', ctx);
55
+ assert.equal(result, true);
56
+ });
57
+
58
+ it('returns false when no critical patterns found', () => {
59
+ const ctx = {
60
+ review: {
61
+ issues: [{ severity: 'low', msg: 'minor issue' }]
62
+ }
63
+ };
64
+ const result = resolveExpression('hasCriticalIssues(review)', ctx);
65
+ assert.equal(result, false);
66
+ });
67
+
68
+ it('returns false for empty object', () => {
69
+ const ctx = {
70
+ review: {}
71
+ };
72
+ const result = resolveExpression('hasCriticalIssues(review)', ctx);
73
+ assert.equal(result, false);
74
+ });
75
+
76
+ it('returns false for null/undefined', () => {
77
+ const ctx = {
78
+ review: null
79
+ };
80
+ const result = resolveExpression('hasCriticalIssues(review)', ctx);
81
+ assert.equal(result, false);
82
+ });
83
+
84
+ it('detects multiple critical patterns in same object', () => {
85
+ const ctx = {
86
+ review: {
87
+ issues: [
88
+ { severity: 'critical', msg: 'bug 1' },
89
+ { priority: 'P0', msg: 'bug 2' },
90
+ { status: 'MUST_FIX', msg: 'bug 3' }
91
+ ]
92
+ }
93
+ };
94
+ const result = resolveExpression('hasCriticalIssues(review)', ctx);
95
+ assert.equal(result, true);
96
+ });
97
+
98
+ it('works with deeply nested critical patterns', () => {
99
+ const ctx = {
100
+ review: {
101
+ results: {
102
+ branches: {
103
+ security: {
104
+ findings: [{ level: 'critical', detail: 'SQL injection' }]
105
+ }
106
+ }
107
+ }
108
+ }
109
+ };
110
+ const result = resolveExpression('hasCriticalIssues(review)', ctx);
111
+ assert.equal(result, true);
112
+ });
113
+
114
+ it('works with string input instead of object', () => {
115
+ const ctx = {
116
+ report: '{"status":"P0","message":"critical error"}'
117
+ };
118
+ const result = resolveExpression('hasCriticalIssues(report)', ctx);
119
+ assert.equal(result, true);
120
+ });
121
+
122
+ it('detects patterns in array of strings', () => {
123
+ const ctx = {
124
+ tags: ['urgent', 'needs-review', 'URGENT']
125
+ };
126
+ const result = resolveExpression('hasCriticalIssues(tags)', ctx);
127
+ assert.equal(result, true);
128
+ });
129
+
130
+ it('returns false for safe array of strings', () => {
131
+ const ctx = {
132
+ tags: ['low-priority', 'needs-review', 'documentation']
133
+ };
134
+ const result = resolveExpression('hasCriticalIssues(tags)', ctx);
135
+ assert.equal(result, false);
136
+ });
137
+ });
@@ -12,7 +12,7 @@
12
12
  },
13
13
 
14
14
  "sync_metadata": {
15
- "last_audit": "2026-02-08",
15
+ "last_audit": "2026-02-09",
16
16
  "audit_result": {
17
17
  "total_nodes": 44,
18
18
  "implemented": 42,
@@ -21,7 +21,7 @@
21
21
  "deprecated": 2,
22
22
  "implementation_rate": "95%"
23
23
  },
24
- "note": "M5-P2: Added design_ui + interrupt_design_complete (2 new implemented nodes). 42 implemented, 2 deprecated (teams_spawn, interrupt_teams_failed). design_ui is optional side-branch, not on mandatory path."
24
+ "note": "M5-P3: Fixed teams profile to bypass deprecated nodes. interview_teams now routes to standard_analyze instead of teams_spawn. Agent Teams enforced inline via callbacks.js at parallel nodes. 2 deprecated nodes (teams_spawn, interrupt_teams_failed) kept for test coverage but unreachable in normal flow."
25
25
  },
26
26
 
27
27
  "inputs": {
@@ -243,7 +243,7 @@
243
243
  "task": "{{inputs.task}}",
244
244
  "topics": "prioridades del equipo y qué entregar primero, deadline y nivel de calidad esperado, áreas que necesitan más atención (frontend/backend/testing), integraciones críticas que no pueden fallar"
245
245
  },
246
- "next": "teams_spawn"
246
+ "next": "standard_analyze"
247
247
  },
248
248
 
249
249
  {
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elsabro",
3
- "version": "7.3.0",
3
+ "version": "7.3.1",
4
4
  "description": "Sistema de desarrollo AI-powered para Claude Code - BMAD Method Integration, Spec-Driven Development, Party Mode, Next Step Suggestions, Stitch UI Design, Agent Teams, blocking code review, orquestación avanzada con flows declarativos",
5
5
  "bin": {
6
6
  "elsabro": "bin/install.js",