iriai-build 0.3.0 → 0.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.
- package/package.json +1 -1
- package/v3/orchestrator.js +140 -109
package/package.json
CHANGED
package/v3/orchestrator.js
CHANGED
|
@@ -469,67 +469,43 @@ export class Orchestrator {
|
|
|
469
469
|
this._notifyOperatorOfDecision(feature, planDecision);
|
|
470
470
|
}
|
|
471
471
|
} else if (role === "ux-designer") {
|
|
472
|
-
//
|
|
473
|
-
await this.adapter.postPipelineMessage(feature.id,
|
|
474
|
-
`UX Designer complete. Starting UI Designer...`);
|
|
475
|
-
queries.updateFeaturePlanningRole(feature.id, "ui-designer");
|
|
476
|
-
this.dispatchPlanningRole(feature.id, "ui-designer");
|
|
477
|
-
} else if (role === "ui-designer") {
|
|
478
|
-
// Compound design step: ui-designer done → surface as "designer" phase-review
|
|
479
|
-
// Both sub-roles are complete; present combined output for user approval
|
|
480
|
-
await this._requestPhaseReview(feature, "designer", output);
|
|
481
|
-
} else if (role === "architect") {
|
|
482
|
-
// Architect validation: ensure structured plan directory before review gate.
|
|
483
|
-
// The architect often drifts to writing a monolithic architecture doc
|
|
484
|
-
// instead of the structured plan directory (plan.yaml + phases/ + task files).
|
|
485
|
-
// Validate required artifacts and redispatch if incomplete.
|
|
472
|
+
// Validate UX designer output before proceeding to UI designer
|
|
486
473
|
const planDir = path.join(feature.signal_dir, "plans");
|
|
487
|
-
const validation = this.
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
474
|
+
const validation = this._validatePlanningOutput(role, planDir);
|
|
475
|
+
if (!validation.pass) {
|
|
476
|
+
await this.adapter.postPipelineMessage(feature.id,
|
|
477
|
+
`UX Designer output incomplete (missing: ${validation.missing.join(", ")}). Redispatching...`);
|
|
478
|
+
this._redispatchForMissingArtifacts(feature, role, validation);
|
|
491
479
|
} else {
|
|
492
|
-
//
|
|
493
|
-
console.log(`[orchestrator] Architect output incomplete for ${slug}: missing ${validation.missing.join(", ")}`);
|
|
480
|
+
// Compound design step: ux-designer done → dispatch ui-designer (no phase-review yet)
|
|
494
481
|
await this.adapter.postPipelineMessage(feature.id,
|
|
495
|
-
`
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
`
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
`WHAT IS MISSING (you must create these):`,
|
|
511
|
-
validation.missing.map(f => ` ✗ ${f}`).join("\n"),
|
|
512
|
-
``,
|
|
513
|
-
`INSTRUCTIONS:`,
|
|
514
|
-
`1. Read $PLAN_DIR/context.md for your investigation notes — do NOT re-explore the entire codebase`,
|
|
515
|
-
`2. Do targeted source code reads ONLY where you need specific file paths, function signatures, or line numbers for task files`,
|
|
516
|
-
`3. Produce plan.yaml with phase DAG, role assignments, and journey refs`,
|
|
517
|
-
`4. Create phases/ directory with structured task files (YAML frontmatter + detailed instructions)`,
|
|
518
|
-
`5. Create journeys/ directory with test journeys (happy-path, failure, regression)`,
|
|
519
|
-
`6. Every task file must cite real file paths and code evidence from the codebase`,
|
|
520
|
-
`7. Signal .done + .output when complete`,
|
|
521
|
-
``,
|
|
522
|
-
`Your context.md is the foundation. Build the structured plan ON TOP of it.`,
|
|
523
|
-
].join("\n");
|
|
524
|
-
|
|
525
|
-
writeSignal(path.join(signalDir, SIGNAL.USER_MESSAGE), redispatchMsg);
|
|
526
|
-
queries.updateFeaturePlanningRole(feature.id, "architect");
|
|
527
|
-
// Fresh context — investigation session is done, architect needs full budget for structured output
|
|
528
|
-
this.dispatchPlanningRole(feature.id, "architect", { continue: false });
|
|
482
|
+
`UX Designer complete. Starting UI Designer...`);
|
|
483
|
+
queries.updateFeaturePlanningRole(feature.id, "ui-designer");
|
|
484
|
+
this.dispatchPlanningRole(feature.id, "ui-designer");
|
|
485
|
+
}
|
|
486
|
+
} else if (role === "ui-designer") {
|
|
487
|
+
// Validate UI designer output before surfacing combined design review
|
|
488
|
+
const planDir = path.join(feature.signal_dir, "plans");
|
|
489
|
+
const validation = this._validatePlanningOutput(role, planDir);
|
|
490
|
+
if (!validation.pass) {
|
|
491
|
+
await this.adapter.postPipelineMessage(feature.id,
|
|
492
|
+
`UI Designer output incomplete (missing: ${validation.missing.join(", ")}). Redispatching...`);
|
|
493
|
+
this._redispatchForMissingArtifacts(feature, role, validation);
|
|
494
|
+
} else {
|
|
495
|
+
// Both sub-roles complete; present combined output for user approval
|
|
496
|
+
await this._requestPhaseReview(feature, "designer", output);
|
|
529
497
|
}
|
|
530
498
|
} else {
|
|
531
|
-
//
|
|
532
|
-
|
|
499
|
+
// All other roles (pm, architect, designer): validate then review gate
|
|
500
|
+
const planDir = path.join(feature.signal_dir, "plans");
|
|
501
|
+
const validation = this._validatePlanningOutput(role, planDir);
|
|
502
|
+
if (!validation.pass) {
|
|
503
|
+
await this.adapter.postPipelineMessage(feature.id,
|
|
504
|
+
`${PLANNING_ROLE_LABELS[role] || role} output incomplete (missing: ${validation.missing.join(", ")}). Redispatching...`);
|
|
505
|
+
this._redispatchForMissingArtifacts(feature, role, validation);
|
|
506
|
+
} else {
|
|
507
|
+
await this._requestPhaseReview(feature, role, output);
|
|
508
|
+
}
|
|
533
509
|
}
|
|
534
510
|
|
|
535
511
|
} finally {
|
|
@@ -539,69 +515,124 @@ export class Orchestrator {
|
|
|
539
515
|
}
|
|
540
516
|
|
|
541
517
|
/**
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
* Returns { pass: boolean, found: string[], missing: string[] }
|
|
518
|
+
* Artifact requirements per planning role.
|
|
519
|
+
* Each entry: { label, check(planDir) → boolean }
|
|
545
520
|
*/
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
521
|
+
static ROLE_ARTIFACTS = {
|
|
522
|
+
pm: [
|
|
523
|
+
{ label: "PRD document (*-prd.md)", check: (d) => !!findArtifact("prd", d) },
|
|
524
|
+
],
|
|
525
|
+
"ux-designer": [
|
|
526
|
+
{ label: "design-decisions.md", check: (d) => !!findArtifact("design-decisions", d) },
|
|
527
|
+
],
|
|
528
|
+
"ui-designer": [
|
|
529
|
+
{ label: "design-decisions.md (with Visual Design Language)", check: (d) => {
|
|
530
|
+
const content = findArtifact("design-decisions", d);
|
|
531
|
+
if (!content) return false;
|
|
532
|
+
try {
|
|
533
|
+
const text = fs.readFileSync(content, "utf-8");
|
|
534
|
+
return text.includes("Visual Design Language") || text.includes("Color Palette");
|
|
535
|
+
} catch { return false; }
|
|
536
|
+
}},
|
|
537
|
+
{ label: "mockup.html", check: (d) => fs.existsSync(path.join(d, "mockup.html")) },
|
|
538
|
+
],
|
|
539
|
+
designer: [
|
|
540
|
+
{ label: "design-decisions.md", check: (d) => !!findArtifact("design-decisions", d) },
|
|
541
|
+
{ label: "mockup.html", check: (d) => fs.existsSync(path.join(d, "mockup.html")) },
|
|
542
|
+
],
|
|
543
|
+
architect: [
|
|
544
|
+
{ label: "context.md (investigation notes)", check: (d) => !!findArtifact("context", d) || !!findArtifact("architecture", d) },
|
|
545
|
+
{ label: "plan.yaml (phase DAG)", check: (d) => fs.existsSync(path.join(d, "plan.yaml")) },
|
|
546
|
+
{ label: "phases/ (task files)", check: (d) => {
|
|
547
|
+
try {
|
|
548
|
+
return fs.readdirSync(path.join(d, "phases"), { withFileTypes: true }).some(e => e.isDirectory());
|
|
549
|
+
} catch { return false; }
|
|
550
|
+
}},
|
|
551
|
+
{ label: "journeys/ (test journeys)", check: (d) => {
|
|
552
|
+
try {
|
|
553
|
+
return fs.readdirSync(path.join(d, "journeys")).some(e => e.endsWith(".md"));
|
|
554
|
+
} catch { return false; }
|
|
555
|
+
}},
|
|
556
|
+
],
|
|
557
|
+
};
|
|
549
558
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
559
|
+
/**
|
|
560
|
+
* Validate that a planning role produced its required artifacts.
|
|
561
|
+
* Runs auto-normalization (e.g. architecture.md → context.md) before checking.
|
|
562
|
+
* Returns { pass, found, missing }
|
|
563
|
+
*/
|
|
564
|
+
_validatePlanningOutput(role, planDir) {
|
|
565
|
+
// Auto-normalize known filename drift
|
|
566
|
+
if (role === "architect") {
|
|
567
|
+
const archFile = path.join(planDir, "architecture.md");
|
|
568
|
+
const contextFile = path.join(planDir, "context.md");
|
|
569
|
+
if (fs.existsSync(archFile) && !fs.existsSync(contextFile)) {
|
|
570
|
+
try {
|
|
571
|
+
fs.renameSync(archFile, contextFile);
|
|
572
|
+
console.log(`[orchestrator] Renamed architecture.md → context.md in ${planDir}`);
|
|
573
|
+
} catch { /* ok */ }
|
|
574
|
+
}
|
|
558
575
|
}
|
|
559
576
|
|
|
560
|
-
|
|
561
|
-
if (
|
|
562
|
-
found.push("context.md (investigation notes)");
|
|
563
|
-
} else {
|
|
564
|
-
missing.push("context.md (investigation notes)");
|
|
565
|
-
}
|
|
577
|
+
const artifacts = Orchestrator.ROLE_ARTIFACTS[role];
|
|
578
|
+
if (!artifacts) return { pass: true, found: [], missing: [] };
|
|
566
579
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
580
|
+
const found = [];
|
|
581
|
+
const missing = [];
|
|
582
|
+
for (const art of artifacts) {
|
|
583
|
+
if (art.check(planDir)) {
|
|
584
|
+
found.push(art.label);
|
|
585
|
+
} else {
|
|
586
|
+
missing.push(art.label);
|
|
587
|
+
}
|
|
572
588
|
}
|
|
573
589
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
let hasPhases = false;
|
|
577
|
-
try {
|
|
578
|
-
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
|
579
|
-
hasPhases = entries.some(e => e.isDirectory());
|
|
580
|
-
} catch { /* doesn't exist */ }
|
|
581
|
-
if (hasPhases) {
|
|
582
|
-
found.push("phases/ (task files)");
|
|
583
|
-
} else {
|
|
584
|
-
missing.push("phases/ directory with task files");
|
|
585
|
-
}
|
|
590
|
+
return { pass: missing.length === 0, found, missing };
|
|
591
|
+
}
|
|
586
592
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
593
|
+
/**
|
|
594
|
+
* Redispatch a planning role that produced incomplete output.
|
|
595
|
+
* Writes focused instructions to .user-message and re-spawns with fresh context.
|
|
596
|
+
*/
|
|
597
|
+
_redispatchForMissingArtifacts(feature, role, validation) {
|
|
598
|
+
const slug = feature.slug;
|
|
599
|
+
console.log(`[orchestrator] ${role} output incomplete for ${slug}: missing ${validation.missing.join(", ")}`);
|
|
600
|
+
|
|
601
|
+
const signalDir = path.join(feature.signal_dir, "planning", role);
|
|
602
|
+
ensureDir(signalDir);
|
|
603
|
+
|
|
604
|
+
const roleName = PLANNING_ROLE_LABELS[role] || role;
|
|
605
|
+
const lines = [
|
|
606
|
+
`INCOMPLETE OUTPUT — REDISPATCH`,
|
|
607
|
+
``,
|
|
608
|
+
`You signaled .done but required artifacts are missing.`,
|
|
609
|
+
`Read your CLAUDE.md output format section and produce the missing items.`,
|
|
610
|
+
``,
|
|
611
|
+
`WHAT EXISTS (do NOT redo):`,
|
|
612
|
+
...validation.found.map(f => ` ✓ ${f}`),
|
|
613
|
+
``,
|
|
614
|
+
`WHAT IS MISSING (you MUST create these):`,
|
|
615
|
+
...validation.missing.map(f => ` ✗ ${f}`),
|
|
616
|
+
``,
|
|
617
|
+
`Write the missing artifacts to $PLAN_DIR/, then signal .done + .output again.`,
|
|
618
|
+
];
|
|
619
|
+
|
|
620
|
+
// Role-specific guidance
|
|
621
|
+
if (role === "architect") {
|
|
622
|
+
lines.push(
|
|
623
|
+
``,
|
|
624
|
+
`ARCHITECT-SPECIFIC:`,
|
|
625
|
+
`1. Read $PLAN_DIR/context.md for your investigation notes — do NOT re-explore the entire codebase`,
|
|
626
|
+
`2. Do targeted source code reads ONLY where needed for specific file paths or function signatures`,
|
|
627
|
+
`3. Every task file must cite real file paths and code evidence from the codebase`,
|
|
628
|
+
);
|
|
598
629
|
}
|
|
599
630
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
};
|
|
631
|
+
writeSignal(path.join(signalDir, SIGNAL.USER_MESSAGE), lines.join("\n"));
|
|
632
|
+
queries.updateFeaturePlanningRole(feature.id, role);
|
|
633
|
+
|
|
634
|
+
// Fresh context for the structured output pass
|
|
635
|
+
this.dispatchPlanningRole(feature.id, role, { continue: false });
|
|
605
636
|
}
|
|
606
637
|
|
|
607
638
|
/**
|