agentsys 5.4.0 → 5.5.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.
@@ -297,375 +297,10 @@ function transformForCodex(content, options) {
297
297
  return content;
298
298
  }
299
299
 
300
- /**
301
- * Transform content for Cursor (.mdc rule files).
302
- *
303
- * MDC format uses YAML frontmatter with `description`, `globs`, and
304
- * `alwaysApply` fields followed by a markdown body.
305
- *
306
- * @param {string} content - Source markdown content (may have frontmatter)
307
- * @param {Object} options
308
- * @param {string} options.description - Rule description
309
- * @param {string} options.pluginInstallPath - Absolute path to plugin install dir
310
- * @param {string} [options.globs] - Optional glob pattern for file matching
311
- * @param {boolean} [options.alwaysApply] - Whether rule always applies (default true)
312
- * @returns {string} Transformed MDC content
313
- */
314
- function transformRuleForCursor(content, options) {
315
- const { description = '', pluginInstallPath, globs = '', alwaysApply = true } = options;
316
-
317
- // Strip control characters and escape description for YAML
318
- const cleanDescription = description.replace(/[\x00-\x1f\x7f]/g, ' ');
319
- const escapedDescription = cleanDescription.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
320
- const yamlDescription = `"${escapedDescription}"`;
321
-
322
- // Build MDC frontmatter
323
- let frontmatter = `---\ndescription: ${yamlDescription}\n`;
324
- if (globs) {
325
- frontmatter += `globs: ${JSON.stringify(globs)}\n`;
326
- }
327
- frontmatter += `alwaysApply: ${alwaysApply}\n---\n`;
328
-
329
- // Strip existing frontmatter if present
330
- if (content.startsWith('---')) {
331
- content = content.replace(/^---\n[\s\S]*?\n---\n?/, '');
332
- }
333
-
334
- content = frontmatter + content;
335
-
336
- // Replace PLUGIN_ROOT paths with actual install path
337
- // Use function replacement to avoid $ pattern interpretation in replacement string
338
- content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, () => pluginInstallPath);
339
- content = content.replace(/\$CLAUDE_PLUGIN_ROOT/g, () => pluginInstallPath);
340
- content = content.replace(/\$\{PLUGIN_ROOT\}/g, () => pluginInstallPath);
341
- content = content.replace(/\$PLUGIN_ROOT/g, () => pluginInstallPath);
342
-
343
- // Strip Claude-specific syntax: Task tool calls (handles one level of nested braces)
344
- content = content.replace(/await\s+Task\s*\(\s*\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}\s*\);?/g, (match) => {
345
- const agentMatch = match.match(/subagent_type:\s*["'](?:[^"':]+:)?([^"']+)["']/);
346
- if (agentMatch) {
347
- return `Invoke the ${agentMatch[1]} agent`;
348
- }
349
- return '';
350
- });
351
-
352
- // Strip require() statements
353
- content = content.replace(/(?:const|let|var)\s+\{?[^}=\n]+\}?\s*=\s*require\s*\([^)]+\);?/g, '');
354
- content = content.replace(/require\s*\(['"][^'"]+['"]\)/g, '');
355
-
356
- // Strip plugin namespacing (e.g. next-task:agent-name -> agent-name)
357
- content = content.replace(/(?:next-task|deslop|ship|sync-docs|audit-project|enhance|perf|repo-map|drift-detect|consult|debate|learn|web-ctl):([a-z][a-z0-9-]*)/g, '$1');
358
-
359
- return content;
360
- }
361
-
362
- /**
363
- * Transform skill content for Cursor.
364
- *
365
- * Minimal transform - Cursor reads SKILL.md frontmatter natively so we
366
- * preserve it. Only replaces PLUGIN_ROOT paths and strips namespace prefixes.
367
- *
368
- * @param {string} content - Source SKILL.md content
369
- * @param {Object} options
370
- * @param {string} options.pluginInstallPath - Absolute path to plugin install dir
371
- * @returns {string} Transformed skill content
372
- */
373
- function transformSkillForCursor(content, options) {
374
- const { pluginInstallPath } = options;
375
-
376
- // Replace PLUGIN_ROOT paths with actual install path
377
- content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, () => pluginInstallPath);
378
- content = content.replace(/\$CLAUDE_PLUGIN_ROOT/g, () => pluginInstallPath);
379
- content = content.replace(/\$\{PLUGIN_ROOT\}/g, () => pluginInstallPath);
380
- content = content.replace(/\$PLUGIN_ROOT/g, () => pluginInstallPath);
381
-
382
- // Strip plugin namespacing (e.g. next-task:agent-name -> agent-name)
383
- content = content.replace(/(?:next-task|deslop|ship|sync-docs|audit-project|enhance|perf|repo-map|drift-detect|consult|debate|learn|web-ctl):([a-z][a-z0-9-]*)/g, '$1');
384
-
385
- return content;
386
- }
387
-
388
- /**
389
- * Transform command content for Cursor.
390
- *
391
- * Light transform - strips frontmatter, replaces PLUGIN_ROOT paths,
392
- * removes require() statements and Task() calls, strips namespace prefixes.
393
- *
394
- * @param {string} content - Source command markdown content
395
- * @param {Object} options
396
- * @param {string} options.pluginInstallPath - Absolute path to plugin install dir
397
- * @returns {string} Transformed command content
398
- */
399
- function transformCommandForCursor(content, options) {
400
- const { pluginInstallPath } = options;
401
-
402
- // Strip existing frontmatter if present
403
- if (content.startsWith('---')) {
404
- content = content.replace(/^---\n[\s\S]*?\n---\n?/, '');
405
- }
406
-
407
- // Replace PLUGIN_ROOT paths with actual install path
408
- content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, () => pluginInstallPath);
409
- content = content.replace(/\$CLAUDE_PLUGIN_ROOT/g, () => pluginInstallPath);
410
- content = content.replace(/\$\{PLUGIN_ROOT\}/g, () => pluginInstallPath);
411
- content = content.replace(/\$PLUGIN_ROOT/g, () => pluginInstallPath);
412
-
413
- // Strip require() statements
414
- content = content.replace(/(?:const|let|var)\s+\{?[^}=\n]+\}?\s*=\s*require\s*\([^)]+\);?/g, '');
415
- content = content.replace(/require\s*\(['"][^'"]+['"]\)/g, '');
416
-
417
- // Strip Claude-specific syntax: Task tool calls (handles one level of nested braces)
418
- content = content.replace(/await\s+Task\s*\(\s*\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}\s*\);?/g, (match) => {
419
- const agentMatch = match.match(/subagent_type:\s*["'](?:[^"':]+:)?([^"']+)["']/);
420
- if (agentMatch) {
421
- return `Invoke the ${agentMatch[1]} agent`;
422
- }
423
- return '';
424
- });
425
-
426
- // Strip plugin namespacing (e.g. next-task:agent-name -> agent-name)
427
- content = content.replace(/(?:next-task|deslop|ship|sync-docs|audit-project|enhance|perf|repo-map|drift-detect|consult|debate|learn|web-ctl):([a-z][a-z0-9-]*)/g, '$1');
428
-
429
- return content;
430
- }
431
-
432
- /**
433
- * Transform skill content for Kiro.
434
- *
435
- * Minimal transform - Kiro reads standard SKILL.md format natively so we
436
- * preserve it. Only replaces PLUGIN_ROOT paths and strips namespace prefixes.
437
- *
438
- * @param {string} content - Source SKILL.md content
439
- * @param {Object} options
440
- * @param {string} options.pluginInstallPath - Absolute path to plugin install dir
441
- * @returns {string} Transformed skill content
442
- */
443
- function transformSkillForKiro(content, options) {
444
- const { pluginInstallPath } = options;
445
-
446
- content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, () => pluginInstallPath);
447
- content = content.replace(/\$CLAUDE_PLUGIN_ROOT/g, () => pluginInstallPath);
448
- content = content.replace(/\$\{PLUGIN_ROOT\}/g, () => pluginInstallPath);
449
- content = content.replace(/\$PLUGIN_ROOT/g, () => pluginInstallPath);
450
-
451
- content = content.replace(/(?:next-task|deslop|ship|sync-docs|audit-project|enhance|perf|repo-map|drift-detect|consult|debate|learn|web-ctl):([a-z][a-z0-9-]*)/g, '$1');
452
-
453
- return content;
454
- }
455
-
456
- /**
457
- * Transform command content for Kiro prompt files.
458
- *
459
- * Strips existing frontmatter, prepends inclusion: manual frontmatter,
460
- * replaces PLUGIN_ROOT paths, removes require()/Task() calls, strips namespaces.
461
- */
462
- function transformCommandForKiro(content, options) {
463
- const { pluginInstallPath, name = '', description = '' } = options;
464
-
465
- if (content.startsWith('---')) {
466
- content = content.replace(/^---\n[\s\S]*?\n---\n?/, '');
467
- }
468
-
469
- const cleanDescription = description.replace(/[\x00-\x1f\x7f]/g, ' ');
470
- const escapedDescription = cleanDescription.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
471
- let frontmatter = '---\n';
472
- frontmatter += 'inclusion: manual\n';
473
- if (name) frontmatter += `name: "${name}"\n`;
474
- if (description) frontmatter += `description: "${escapedDescription}"\n`;
475
- frontmatter += '---\n';
476
-
477
- content = frontmatter + content;
478
-
479
- content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, () => pluginInstallPath);
480
- content = content.replace(/\$CLAUDE_PLUGIN_ROOT/g, () => pluginInstallPath);
481
- content = content.replace(/\$\{PLUGIN_ROOT\}/g, () => pluginInstallPath);
482
- content = content.replace(/\$PLUGIN_ROOT/g, () => pluginInstallPath);
483
-
484
- content = content.replace(/(?:const|let|var)\s+\{?[^}=\n]+\}?\s*=\s*require\s*\([^)]+\);?/g, '');
485
- content = content.replace(/require\s*\(['"][^'"]+['"]\)/g, '');
486
-
487
- // Transform code blocks containing Promise.all + Task() (parallel reviewer spawns).
488
- // The bare Task() regex below can't reach inside fenced code blocks.
489
- // Fence boundaries must be at line start (^```) to avoid matching backtick
490
- // template literals inside the code as false fence endings.
491
- content = content.replace(/^```(?:javascript|js)?\n([\s\S]*?)^```$/gm, (fullBlock, codeContent) => {
492
- if (!codeContent.includes('Promise.all') || !codeContent.includes('Task(')) return fullBlock;
493
- // Use lazy [\s\S]*? for the template literal body: it stops at the first backtick and
494
- // naturally matches any character including standalone $ (e.g. $100, $BUDGET).
495
- const taskMatches = [...codeContent.matchAll(/Task\s*\(\s*\{[\s\S]*?subagent_type:\s*['"](?:[^"':]+:)?([^'"]+)['"][\s\S]*?prompt:\s*`([\s\S]*?)`/gs)];
496
- if (taskMatches.length < 2) return fullBlock;
497
-
498
- const delegations = taskMatches.map(m => {
499
- const agent = m[1];
500
- const promptFirstLine = m[2].split('\n').find(l => l.trim()) || '';
501
- return `Delegate to the \`${agent}\` subagent:\n> ${promptFirstLine.trim()}`;
502
- });
503
-
504
- let result = delegations.join('\n\n');
505
-
506
- const hasReviewKeyword = delegations.some(d =>
507
- /review|quality|security|performance|test|coverage/i.test(d)
508
- );
509
- if (delegations.length >= 4 && hasReviewKeyword) {
510
- result = `**Review phase (Kiro - max 4 agents, fallback to 2 sequential):**\n\nTry delegating to these subagents (experimental parallel spawning):\n\n${result}\n\nIf parallel spawning is unavailable, run 2 combined reviewers sequentially:\n1. Delegate to the \`reviewer-quality-security\` subagent (code quality + security)\n2. Then delegate to the \`reviewer-perf-test\` subagent (performance + test coverage)\n\nAggregate all findings from whichever execution path succeeded.`;
511
- }
512
-
513
- return result;
514
- });
515
-
516
- // Transform bare Task() calls outside code blocks
517
- content = content.replace(/await\s+Task\s*\(\s*\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}\s*\);?/g, (match) => {
518
- const agentMatch = match.match(/subagent_type:\s*["'](?:[^"':]+:)?([^"']+)["']/);
519
- const promptMatch = match.match(/prompt:\s*[`"']([\s\S]*?)[`"']/);
520
- if (agentMatch) {
521
- const agentName = agentMatch[1];
522
- const prompt = promptMatch ? promptMatch[1].replace(/\\n/g, '\n').trim() : '';
523
- if (prompt) {
524
- return `Delegate to the \`${agentName}\` subagent:\n> ${prompt.split('\n')[0]}`;
525
- }
526
- return `Delegate to the \`${agentName}\` subagent.`;
527
- }
528
- return '';
529
- });
530
-
531
- // Transform AskUserQuestion to markdown prompt for Kiro chat
532
- content = content.replace(/(?:await\s+)?AskUserQuestion\s*\(\s*\{[\s\S]*?\}\s*\);?/g, (match) => {
533
- const questionMatch = match.match(/question:\s*["'`]([\s\S]*?)["'`]/);
534
- const question = questionMatch ? questionMatch[1] : 'Please choose:';
535
- const optionMatches = [...match.matchAll(/label:\s*["'`]([^"'`]+)["'`][\s\S]*?description:\s*["'`]([^"'`]+)["'`]/g)];
536
- if (optionMatches.length > 0) {
537
- const options = optionMatches.map((m, i) => `${i + 1}. **${m[1]}** - ${m[2]}`).join('\n');
538
- return `**${question}**\n\n${options}\n\nReply with the number or name of your choice.`;
539
- }
540
- return `**${question}**\n\nReply in chat with your choice.`;
541
- });
542
-
543
- content = content.replace(/(?:next-task|deslop|ship|sync-docs|audit-project|enhance|perf|repo-map|drift-detect|consult|debate|learn|web-ctl):([a-z][a-z0-9-]*)/g, '$1');
544
-
545
- // Batch parallel reviewer delegations for Kiro's agent limit.
546
- // Detects 4+ consecutive "Delegate to" lines with review-related names
547
- // and rewrites as try-4-then-fallback-to-2 pattern.
548
- const reviewerBatchPattern = /((?:Delegate to the `[^`]*` subagent[^\n]*\n){4,})/g;
549
- content = content.replace(reviewerBatchPattern, (block) => {
550
- const delegations = block.match(/Delegate to the `([^`]+)` subagent/g) || [];
551
- if (delegations.length < 4) return block;
552
-
553
- const hasReviewKeyword = delegations.some(d =>
554
- /review|quality|security|performance|test|coverage/i.test(d)
555
- );
556
- if (!hasReviewKeyword) return block;
557
-
558
- return `**Review phase (Kiro - max 4 agents, fallback to 2 sequential):**
559
-
560
- Try delegating to these subagents (experimental parallel spawning):
561
- ${block}
562
- If parallel spawning is unavailable, run 2 combined reviewers sequentially:
563
- 1. Delegate to the \`reviewer-quality-security\` subagent (code quality + security)
564
- 2. Then delegate to the \`reviewer-perf-test\` subagent (performance + test coverage)
565
-
566
- Aggregate all findings from whichever execution path succeeded.\n`;
567
- });
568
-
569
- return content;
570
- }
571
-
572
- /**
573
- * Transform agent markdown+frontmatter to Kiro JSON format.
574
- *
575
- * Parses frontmatter for name/description/model/tools, uses body as prompt.
576
- * Returns a JSON string matching Kiro's agent schema.
577
- */
578
- function transformAgentForKiro(content, options) {
579
- const { pluginInstallPath } = options || {};
580
-
581
- const frontmatter = discovery.parseFrontmatter(content);
582
- let body = content;
583
- if (content.startsWith('---')) {
584
- const endIdx = content.indexOf('\n---', 3);
585
- if (endIdx !== -1) {
586
- body = content.substring(endIdx + 4).replace(/^\n/, '');
587
- }
588
- }
589
-
590
- if (pluginInstallPath) {
591
- body = body.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, () => pluginInstallPath);
592
- body = body.replace(/\$CLAUDE_PLUGIN_ROOT/g, () => pluginInstallPath);
593
- body = body.replace(/\$\{PLUGIN_ROOT\}/g, () => pluginInstallPath);
594
- body = body.replace(/\$PLUGIN_ROOT/g, () => pluginInstallPath);
595
- }
596
-
597
- body = body.replace(/(?:next-task|deslop|ship|sync-docs|audit-project|enhance|perf|repo-map|drift-detect|consult|debate|learn|web-ctl):([a-z][a-z0-9-]*)/g, '$1');
598
-
599
- const agent = {
600
- name: frontmatter.name || '',
601
- description: frontmatter.description || '',
602
- prompt: body.trim()
603
- };
604
-
605
- if (frontmatter.tools) {
606
- // parseFrontmatter returns arrays for YAML list syntax, string for inline
607
- const toolItems = Array.isArray(frontmatter.tools)
608
- ? frontmatter.tools.map(t => t.toLowerCase())
609
- : [frontmatter.tools.toLowerCase()];
610
- const toolStr = toolItems.join(' ');
611
- const tools = [];
612
- if (toolStr.includes('read')) tools.push('read');
613
- if (toolStr.includes('edit') || toolStr.includes('write')) tools.push('write');
614
- if (toolStr.includes('bash') || toolStr.includes('shell')) tools.push('shell');
615
- if (toolStr.includes('glob')) tools.push('read');
616
- if (toolStr.includes('grep')) tools.push('read');
617
- if (toolStr.includes('task') || toolStr.includes('agent')) tools.push('shell');
618
- if (toolStr.includes('web') || toolStr.includes('fetch')) tools.push('shell');
619
- if (toolStr.includes('notebook')) tools.push('write');
620
- if (toolStr.includes('lsp')) tools.push('read');
621
- const deduped = [...new Set(tools)];
622
- agent.tools = deduped.length > 0 ? deduped : ['read'];
623
- } else {
624
- agent.tools = ['read'];
625
- }
626
-
627
- agent.resources = ['file://.kiro/prompts/**/*.md'];
628
-
629
- return JSON.stringify(agent, null, 2);
630
- }
631
-
632
- /**
633
- * Generate a combined reviewer agent JSON for Kiro's 4-agent limit.
634
- * Merges multiple review responsibilities into a single agent.
635
- *
636
- * @param {Array<{name: string, focus: string}>} roles - Review passes to combine
637
- * @param {string} name - Agent name
638
- * @param {string} description - Agent description
639
- * @returns {string} JSON string for .kiro/agents/*.json
640
- */
641
- function generateCombinedReviewerAgent(roles, name, description) {
642
- const sections = roles.map(r =>
643
- `## ${r.name} Review\n\nFocus: ${r.focus}`
644
- ).join('\n\n---\n\n');
645
-
646
- const agent = {
647
- name,
648
- description,
649
- prompt: `You are a combined code reviewer covering multiple review passes in a single session.\n\n${sections}\n\nFor each file you review, check ALL of the above review dimensions. Return findings as a JSON array with objects containing: pass (which review), file, line, severity (critical/high/medium/low), description, suggestion.`,
650
- tools: ['read'],
651
- resources: ['file://.kiro/prompts/**/*.md'],
652
- };
653
-
654
- return JSON.stringify(agent, null, 2);
655
- }
656
-
657
300
  module.exports = {
658
301
  transformBodyForOpenCode,
659
302
  transformCommandFrontmatterForOpenCode,
660
303
  transformAgentFrontmatterForOpenCode,
661
304
  transformSkillBodyForOpenCode,
662
- transformForCodex,
663
- transformRuleForCursor,
664
- transformSkillForCursor,
665
- transformCommandForCursor,
666
- transformForCursor: transformRuleForCursor,
667
- transformSkillForKiro,
668
- transformCommandForKiro,
669
- transformAgentForKiro,
670
- generateCombinedReviewerAgent
305
+ transformForCodex
671
306
  };