workflow-ai 1.0.39 → 1.0.40

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workflow-ai",
3
- "version": "1.0.39",
3
+ "version": "1.0.40",
4
4
  "description": "AI Agent Workflow Coordinator — kanban-based pipeline for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -144,6 +144,13 @@ function checkBacklog(planId) {
144
144
 
145
145
  for (const ticket of tickets) {
146
146
  const { frontmatter, id } = ticket;
147
+
148
+ // Пропускаем тикеты, требующие ручного выполнения
149
+ if (frontmatter.type === 'human') {
150
+ console.log(`[INFO] ${id}: type is 'human', skipping (requires manual execution)`);
151
+ continue;
152
+ }
153
+
147
154
  const conditions = frontmatter.conditions || [];
148
155
  const dependencies = frontmatter.dependencies || [];
149
156
 
@@ -51,6 +51,12 @@ function moveToReady(ticketId) {
51
51
  const content = fs.readFileSync(sourcePath, 'utf8');
52
52
  const { frontmatter, body } = parseFrontmatter(content);
53
53
 
54
+ // Пропускаем тикеты, требующие ручного выполнения
55
+ if (frontmatter.type === 'human') {
56
+ console.log(`[INFO] ${ticketId}: type is 'human', skipping (requires manual execution)`);
57
+ return false;
58
+ }
59
+
54
60
  frontmatter.updated_at = new Date().toISOString();
55
61
 
56
62
  const newContent = serializeFrontmatter(frontmatter) + body;
@@ -523,6 +523,12 @@ function pickNextTicket(planId) {
523
523
  const eligibleTickets = tickets.filter(ticket => {
524
524
  const { frontmatter } = ticket;
525
525
 
526
+ // Пропускаем тикеты, требующие ручного выполнения
527
+ if (frontmatter.type === 'human') {
528
+ logger.info(`Skipping ticket ${ticket.id}: type is 'human' (requires manual execution)`);
529
+ return false;
530
+ }
531
+
526
532
  // Проверка условий
527
533
  const conditions = frontmatter.conditions || [];
528
534
  const conditionsMet = conditions.every(checkCondition);
@@ -573,6 +579,61 @@ function pickNextTicket(planId) {
573
579
  };
574
580
  }
575
581
 
582
+ /**
583
+ * Архивирует все done-тикеты, принадлежащие архивным планам (plans/archive/).
584
+ * Сканирует все планы в plans/archive/, находит их тикеты в done/ и перемещает в archive/.
585
+ */
586
+ function archiveTicketsOfArchivedPlans() {
587
+ const archivedPlansDir = path.join(WORKFLOW_DIR, 'plans', 'archive');
588
+ if (!fs.existsSync(archivedPlansDir)) return { archived: [] };
589
+
590
+ // Собираем ID всех архивных планов
591
+ const archivedPlanIds = new Set();
592
+ const planFiles = fs.readdirSync(archivedPlansDir).filter(f => f.endsWith('.md'));
593
+ for (const file of planFiles) {
594
+ const id = normalizePlanId(file);
595
+ if (id) archivedPlanIds.add(id);
596
+ }
597
+
598
+ if (archivedPlanIds.size === 0) return { archived: [] };
599
+
600
+ if (!fs.existsSync(DONE_DIR)) return { archived: [] };
601
+
602
+ if (!fs.existsSync(ARCHIVE_DIR)) {
603
+ fs.mkdirSync(ARCHIVE_DIR, { recursive: true });
604
+ }
605
+
606
+ const archived = [];
607
+ const files = fs.readdirSync(DONE_DIR).filter(f => f.endsWith('.md') && f !== '.gitkeep.md');
608
+
609
+ for (const file of files) {
610
+ const filePath = path.join(DONE_DIR, file);
611
+ try {
612
+ const content = fs.readFileSync(filePath, 'utf8');
613
+ const { frontmatter, body } = parseFrontmatter(content);
614
+ const ticketPlanId = normalizePlanId(frontmatter.parent_plan);
615
+
616
+ if (!ticketPlanId || !archivedPlanIds.has(ticketPlanId)) continue;
617
+
618
+ const ticketId = frontmatter.id || file.replace('.md', '');
619
+
620
+ frontmatter.updated_at = new Date().toISOString();
621
+ frontmatter.archived_at = new Date().toISOString();
622
+
623
+ const destPath = path.join(ARCHIVE_DIR, file);
624
+ fs.writeFileSync(destPath, serializeFrontmatter(frontmatter) + body, 'utf8');
625
+ fs.unlinkSync(filePath);
626
+
627
+ archived.push(ticketId);
628
+ logger.info(`[ARCHIVE] ${ticketId}: done → archive (plan ${ticketPlanId} is archived)`);
629
+ } catch (e) {
630
+ logger.warn(`Failed to archive ticket ${file}: ${e.message}`);
631
+ }
632
+ }
633
+
634
+ return { archived };
635
+ }
636
+
576
637
  // Main entry point
577
638
  async function main() {
578
639
  const planId = extractPlanId();
@@ -596,6 +657,12 @@ async function main() {
596
657
  logger.info(`Auto-corrected ${correctionResult.moved.length} ticket(s)`);
597
658
  }
598
659
 
660
+ // Архивируем done-тикеты архивных планов
661
+ const archiveResult = archiveTicketsOfArchivedPlans();
662
+ if (archiveResult.archived.length > 0) {
663
+ logger.info(`Archived ${archiveResult.archived.length} ticket(s) from archived plans: ${archiveResult.archived.join(', ')}`);
664
+ }
665
+
599
666
  if (planId) {
600
667
  const closeResult = checkAndClosePlan(WORKFLOW_DIR, planId);
601
668
  if (closeResult.closed) {