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
|
/**
|
package/lib/gateway/router.js
CHANGED
|
@@ -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:
|
|
21
|
-
develop:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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 '
|
|
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;
|