orchestrix-yuri 4.3.0 โ†’ 4.4.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.
@@ -181,6 +181,20 @@ class PhaseOrchestrator {
181
181
  };
182
182
  }
183
183
 
184
+ if (this._phase === 'test') {
185
+ return { phase: 'test', message: '๐Ÿงช Testing in progress. QA running smoke tests.' };
186
+ }
187
+
188
+ if (this._phase === 'iterate') {
189
+ const ctx = this._changeContext || {};
190
+ return { phase: 'iterate', message: `๐Ÿ”„ Iteration in progress. Current: ${ctx.iteratePhase || 'starting'}` };
191
+ }
192
+
193
+ if (this._phase === 'change') {
194
+ const ctx = this._changeContext || {};
195
+ return { phase: 'change', message: `๐Ÿ”ง Change in progress (${ctx.scope || '?'}). Step ${this._step + 1}` };
196
+ }
197
+
184
198
  return { phase: this._phase, message: `Phase ${this._phase} is running.` };
185
199
  }
186
200
 
@@ -753,6 +767,187 @@ class PhaseOrchestrator {
753
767
  this.onComplete('develop', '๐ŸŽ‰ Development complete! All stories finished.\n\nRun *test to start smoke testing.');
754
768
  }
755
769
 
770
+ // โ”€โ”€ Test Phase โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
771
+
772
+ /**
773
+ * Start test phase: QA smoke test per epic, auto fix-retest loop.
774
+ * Uses existing dev session's QA window (3) and Dev window (2).
775
+ */
776
+ startTest(projectRoot) {
777
+ if (this._phase) {
778
+ return `โš ๏ธ Phase "${this._phase}" is already running. Use *status to check.`;
779
+ }
780
+
781
+ this._projectRoot = projectRoot;
782
+ this._phase = 'test';
783
+ this._step = 0;
784
+ this._lastHash = '';
785
+ this._stableCount = 0;
786
+
787
+ // Ensure dev session exists (QA is window 3)
788
+ try {
789
+ const scriptPath = path.join(SKILL_DIR, 'scripts', 'ensure-session.sh');
790
+ const result = execSync(`bash "${scriptPath}" dev "${projectRoot}"`, {
791
+ encoding: 'utf8', timeout: 60000,
792
+ }).trim();
793
+ const lines = result.split('\n');
794
+ this._session = lines[lines.length - 1].trim();
795
+ } catch (err) {
796
+ this._phase = null;
797
+ return `โŒ Failed to ensure dev session: ${err.message}`;
798
+ }
799
+
800
+ // Start QA smoke test on first epic
801
+ tmx.sendKeysWithEnter(this._session, 3, '/clear');
802
+ execSync('sleep 2');
803
+ tmx.sendKeysWithEnter(this._session, 3, '/o qa');
804
+ execSync('sleep 12');
805
+ tmx.sendKeysWithEnter(this._session, 3, '*smoke-test');
806
+
807
+ const pollInterval = this.config.phase_poll_interval || 30000;
808
+ this._timer = setInterval(() => this._pollTest(), pollInterval);
809
+
810
+ log.engine(`Test phase started: session=${this._session}`);
811
+ return '๐Ÿงช Testing started! QA is running smoke tests.\n\nI\'ll notify you of results. Failed tests will auto-trigger Dev fixes.';
812
+ }
813
+
814
+ _pollTest() {
815
+ if (this._phase !== 'test') return;
816
+
817
+ if (!tmx.hasSession(this._session)) {
818
+ this._handleError('test', 'tmux session died');
819
+ return;
820
+ }
821
+
822
+ const result = tmx.checkCompletion(this._session, 3, this._lastHash);
823
+ if (result.status === 'complete' || (result.status === 'stable' && ++this._stableCount >= 3)) {
824
+ this._stableCount = 0;
825
+ this._lastHash = '';
826
+ this._completeTest();
827
+ return;
828
+ }
829
+ if (result.status !== 'stable') { this._stableCount = 0; this._lastHash = result.hash || ''; }
830
+ else { this._lastHash = result.hash; }
831
+ }
832
+
833
+ _completeTest() {
834
+ if (this._timer) { clearInterval(this._timer); this._timer = null; }
835
+ this._phase = null;
836
+ log.engine('Test phase complete');
837
+ this.onComplete('test', '๐Ÿงช Testing complete! Check QA results.\n\nRun *deploy when ready.');
838
+ }
839
+
840
+ // โ”€โ”€ Iterate (New Iteration) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
841
+
842
+ /**
843
+ * Start a new iteration: PM generates next-steps, agents execute, SM starts dev.
844
+ * Flow: PM *start-iteration โ†’ parse HANDOFF โ†’ execute agents โ†’ SM *draft โ†’ dev auto
845
+ */
846
+ startIterate(projectRoot) {
847
+ if (this._phase) {
848
+ return `โš ๏ธ Phase "${this._phase}" is already running. Use *status to check.`;
849
+ }
850
+
851
+ this._projectRoot = projectRoot;
852
+ this._phase = 'iterate';
853
+ this._step = 0;
854
+ this._lastHash = '';
855
+ this._stableCount = 0;
856
+
857
+ // Step 1: Ensure planning session
858
+ try {
859
+ const scriptPath = path.join(SKILL_DIR, 'scripts', 'ensure-session.sh');
860
+ const result = execSync(`bash "${scriptPath}" planning "${projectRoot}"`, {
861
+ encoding: 'utf8', timeout: 60000,
862
+ }).trim();
863
+ const lines = result.split('\n');
864
+ this._session = lines[lines.length - 1].trim();
865
+ } catch (err) {
866
+ this._phase = null;
867
+ return `โŒ Failed to create planning session: ${err.message}`;
868
+ }
869
+
870
+ // Start PM *start-iteration
871
+ tmx.sendKeysWithEnter(this._session, 0, '/o pm');
872
+ execSync('sleep 15');
873
+ tmx.sendKeysWithEnter(this._session, 0, '*start-iteration');
874
+
875
+ this._changeContext = { iteratePhase: 'pm' };
876
+ const pollInterval = this.config.phase_poll_interval || 30000;
877
+ this._timer = setInterval(() => this._pollIterate(), pollInterval);
878
+
879
+ log.engine(`Iterate started: session=${this._session}`);
880
+ return '๐Ÿ”„ New iteration started! PM is generating next-steps.\n\nAfter PM finishes, agents will execute in sequence, then dev automation resumes.';
881
+ }
882
+
883
+ _pollIterate() {
884
+ if (this._phase !== 'iterate') return;
885
+ if (this._waitingForInput) return;
886
+
887
+ const ctx = this._changeContext;
888
+ if (!ctx) return;
889
+
890
+ if (!tmx.hasSession(this._session)) {
891
+ this._handleError('iterate', 'tmux session died');
892
+ return;
893
+ }
894
+
895
+ const result = tmx.checkCompletion(this._session, 0, this._lastHash);
896
+ if (result.status === 'complete' || (result.status === 'stable' && ++this._stableCount >= 3)) {
897
+ this._stableCount = 0;
898
+ this._lastHash = '';
899
+ this._step++;
900
+
901
+ if (ctx.iteratePhase === 'pm') {
902
+ // PM done โ†’ send to Architect for review
903
+ this.onProgress('โœ… PM generated next-steps. Sending to Architect...');
904
+ ctx.iteratePhase = 'architect';
905
+ tmx.sendKeysWithEnter(this._session, 0, '/clear');
906
+ execSync('sleep 2');
907
+ tmx.sendKeysWithEnter(this._session, 0, '/o architect');
908
+ execSync('sleep 15');
909
+ tmx.sendKeysWithEnter(this._session, 0, '*resolve-change');
910
+ } else if (ctx.iteratePhase === 'architect') {
911
+ // Architect done โ†’ transition to dev: SM *draft
912
+ this.onProgress('โœ… Architect resolved. Starting dev automation via SM...');
913
+ try {
914
+ const scriptPath = path.join(SKILL_DIR, 'scripts', 'ensure-session.sh');
915
+ const devResult = execSync(`bash "${scriptPath}" dev "${this._projectRoot}"`, {
916
+ encoding: 'utf8', timeout: 120000,
917
+ }).trim();
918
+ const devLines = devResult.split('\n');
919
+ const devSession = devLines[devLines.length - 1].trim();
920
+
921
+ tmx.sendKeysWithEnter(devSession, 1, '/clear');
922
+ execSync('sleep 2');
923
+ tmx.sendKeysWithEnter(devSession, 1, '/o sm');
924
+ execSync('sleep 12');
925
+ tmx.sendKeysWithEnter(devSession, 1, '*draft');
926
+
927
+ this._completeIterate(devSession);
928
+ } catch (err) {
929
+ this._handleError('iterate', `Failed to start dev: ${err.message}`);
930
+ }
931
+ }
932
+ return;
933
+ }
934
+
935
+ if (result.status !== 'stable') { this._stableCount = 0; this._lastHash = result.hash || ''; }
936
+ else { this._lastHash = result.hash; }
937
+ }
938
+
939
+ _completeIterate(devSession) {
940
+ if (this._timer) { clearInterval(this._timer); this._timer = null; }
941
+ // Kill planning session
942
+ if (this._session && tmx.hasSession(this._session)) {
943
+ tmx.killSession(this._session);
944
+ }
945
+ this._phase = null;
946
+ this._changeContext = null;
947
+ log.engine('Iterate complete โ€” dev automation started');
948
+ this.onComplete('iterate', `๐Ÿ”„ New iteration launched!\n\nSM is drafting stories in dev session: ${devSession}\nAgents will chain automatically via handoff-detector.`);
949
+ }
950
+
756
951
  // โ”€โ”€ Change Management โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
757
952
 
758
953
  /**
@@ -17,15 +17,17 @@ const YURI_GLOBAL = path.join(os.homedir(), '.yuri');
17
17
  // โ”€โ”€ Phase command patterns โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
18
18
 
19
19
  const PHASE_COMMANDS = {
20
- plan: /^\*plan\b/i,
21
- develop: /^\*develop\b/i,
22
- change: /^\*change\s+(.+)/i,
23
- test: /^\*test\b/i,
24
- deploy: /^\*deploy\b/i,
25
- cancel: /^\*cancel\b/i,
20
+ plan: /^\*plan\b/i,
21
+ develop: /^\*develop\b/i,
22
+ test: /^\*test\b/i,
23
+ change: /^\*change\s+(.+)/i,
24
+ iterate: /^\*iterate\b/i,
25
+ deploy: /^\*deploy\b/i,
26
+ cancel: /^\*cancel\b/i,
26
27
  };
27
28
 
28
29
  const META_COMMANDS = {
30
+ help: /^\*help\b/i,
29
31
  projects: /^\*projects\b/i,
30
32
  switch: /^\*switch\s+(.+)/i,
31
33
  };
@@ -208,7 +210,10 @@ class Router {
208
210
  return this._handleStatusQuery(msg);
209
211
  }
210
212
 
211
- // โ•โ•โ• META COMMANDS โ€” *projects, *switch (always allowed) โ•โ•โ•
213
+ // โ•โ•โ• META COMMANDS โ€” *help, *projects, *switch (always allowed) โ•โ•โ•
214
+ if (META_COMMANDS.help.test(msg.text.trim())) {
215
+ return { text: this._buildHelpText() };
216
+ }
212
217
  if (META_COMMANDS.projects.test(msg.text.trim())) {
213
218
  return this._handleProjects(msg);
214
219
  }
@@ -281,9 +286,14 @@ class Router {
281
286
  case 'develop':
282
287
  response = this.orchestrator.startDevelop(projectRoot);
283
288
  break;
289
+ case 'test':
290
+ response = this.orchestrator.startTest(projectRoot);
291
+ break;
284
292
  case 'change':
285
293
  return this._handleChangeCommand(msg, projectRoot);
286
- case 'test':
294
+ case 'iterate':
295
+ response = this.orchestrator.startIterate(projectRoot);
296
+ break;
287
297
  case 'deploy':
288
298
  return this._processMessageDirect(msg);
289
299
  default:
@@ -799,6 +809,46 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
799
809
  return lines.join('\n');
800
810
  }
801
811
 
812
+ // โ”€โ”€ Help โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
813
+
814
+ _buildHelpText() {
815
+ return `๐Ÿš€ **Yuri โ€” Meta-Orchestrator**
816
+
817
+ **Project Lifecycle**
818
+ | Command | Description |
819
+ |---------|-------------|
820
+ | \`*create\` | Create a new project (interactive Q&A) |
821
+ | \`*plan\` | Start planning phase (6 agents sequentially, background) |
822
+ | \`*develop\` | Start development phase (4 agents with HANDOFF, background) |
823
+ | \`*test\` | Start smoke testing (QA per epic, auto fix-retest) |
824
+ | \`*deploy\` | Deploy the project |
825
+
826
+ **Change & Iteration**
827
+ | Command | Description |
828
+ |---------|-------------|
829
+ | \`*change "desc"\` | Handle a requirement change (auto scope assessment) |
830
+ | \`*iterate\` | Start new iteration (PM โ†’ agents โ†’ dev automation) |
831
+
832
+ **Monitoring**
833
+ | Command | Description |
834
+ |---------|-------------|
835
+ | \`*status\` | Show progress card (epic/story/agent/cost) |
836
+ | \`*cancel\` | Stop the running phase |
837
+ | \`*resume\` | Resume from last checkpoint |
838
+
839
+ **Portfolio**
840
+ | Command | Description |
841
+ |---------|-------------|
842
+ | \`*projects\` | List all registered projects |
843
+ | \`*switch <name>\` | Switch active project |
844
+ | \`*help\` | Show this help |
845
+
846
+ **Notes**
847
+ - \`*plan\`, \`*develop\`, \`*test\`, \`*change\`, \`*iterate\` run in background โ€” you can chat normally while they execute
848
+ - Progress is reported every 30 minutes automatically
849
+ - Reply to agent question messages to answer them directly`;
850
+ }
851
+
802
852
  _updateGlobalFocus(msg, projectRoot) {
803
853
  const focusPath = path.join(YURI_GLOBAL, 'focus.yaml');
804
854
  if (!fs.existsSync(focusPath)) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrix-yuri",
3
- "version": "4.3.0",
3
+ "version": "4.4.1",
4
4
  "description": "Yuri โ€” Meta-Orchestrator for Orchestrix. Drive your entire project lifecycle with natural language.",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {