orchestrix-yuri 4.6.5 โ 4.6.7
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.
|
@@ -238,7 +238,8 @@ class PhaseOrchestrator {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
if (this._phase === 'test') {
|
|
241
|
-
|
|
241
|
+
const card = this._buildTestProgressCard();
|
|
242
|
+
return { phase: 'test', message: card };
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
if (this._phase === 'iterate') {
|
|
@@ -947,11 +948,14 @@ class PhaseOrchestrator {
|
|
|
947
948
|
|
|
948
949
|
// Start first epic test
|
|
949
950
|
this._loadAndTestEpic();
|
|
951
|
+
this._testStartedAt = Date.now();
|
|
952
|
+
this._lastReportTime = Date.now();
|
|
950
953
|
|
|
951
954
|
const pollInterval = this.config.phase_poll_interval || 30000;
|
|
952
955
|
this._timer = setInterval(() => this._pollTest(), pollInterval);
|
|
953
956
|
|
|
954
|
-
|
|
957
|
+
const reportMin = Math.round(this._reportInterval / 60000);
|
|
958
|
+
log.engine(`Test phase started: session=${this._session}, epics=${epicIds.join(',')}, startIdx=${startIdx}, report every ${reportMin}min`);
|
|
955
959
|
return `๐งช Testing started! ${epicIds.length} epic(s) to test.\n\nQA will smoke-test each epic. Failed tests auto-trigger Dev fixes (max 3 rounds).\nI'll notify you of results.`;
|
|
956
960
|
}
|
|
957
961
|
|
|
@@ -1054,6 +1058,14 @@ class PhaseOrchestrator {
|
|
|
1054
1058
|
return;
|
|
1055
1059
|
}
|
|
1056
1060
|
|
|
1061
|
+
// Periodic progress report (reuses dev phase's _reportInterval)
|
|
1062
|
+
const now = Date.now();
|
|
1063
|
+
if (now - this._lastReportTime >= this._reportInterval) {
|
|
1064
|
+
this._lastReportTime = now;
|
|
1065
|
+
const card = this._buildTestProgressCard();
|
|
1066
|
+
this.onProgress(card);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1057
1069
|
// Determine which window to watch
|
|
1058
1070
|
const window = ctx.subPhase === 'dev-fixing' ? 2 : 3;
|
|
1059
1071
|
const result = tmx.checkCompletion(this._session, window, this._lastHash);
|
|
@@ -1154,6 +1166,62 @@ class PhaseOrchestrator {
|
|
|
1154
1166
|
}
|
|
1155
1167
|
}
|
|
1156
1168
|
|
|
1169
|
+
/**
|
|
1170
|
+
* Build a progress card for the test phase (used by getStatus + periodic reports).
|
|
1171
|
+
*/
|
|
1172
|
+
_buildTestProgressCard() {
|
|
1173
|
+
const ctx = this._testContext;
|
|
1174
|
+
if (!ctx) return '๐งช Testing in progress.';
|
|
1175
|
+
|
|
1176
|
+
const total = ctx.epicIds.length;
|
|
1177
|
+
const passed = Object.values(ctx.results).filter((r) => r.status === 'passed').length;
|
|
1178
|
+
const failed = Object.values(ctx.results).filter((r) => r.status === 'failed').length;
|
|
1179
|
+
const tested = passed + failed;
|
|
1180
|
+
const pct = total > 0 ? Math.round((tested / total) * 100) : 0;
|
|
1181
|
+
|
|
1182
|
+
const currentEpic = ctx.epicIds[ctx.epicIdx];
|
|
1183
|
+
const subPhaseLabel = {
|
|
1184
|
+
'qa-testing': '๐ QA smoke-testing',
|
|
1185
|
+
'dev-fixing': '๐ง Dev fixing bug',
|
|
1186
|
+
}[ctx.subPhase] || 'โณ Loading agent';
|
|
1187
|
+
|
|
1188
|
+
const elapsed = this._devStartedAt ? this._formatDuration(Date.now() - this._devStartedAt) : '';
|
|
1189
|
+
const startedAt = this._testStartedAt ? this._formatDuration(Date.now() - this._testStartedAt) : '';
|
|
1190
|
+
|
|
1191
|
+
const lines = [
|
|
1192
|
+
`๐งช **Test Phase Progress**`,
|
|
1193
|
+
``,
|
|
1194
|
+
this._progressBar(pct),
|
|
1195
|
+
``,
|
|
1196
|
+
`๐ Epics: ${tested}/${total} tested (โ
${passed} passed, โ ${failed} failed)`,
|
|
1197
|
+
];
|
|
1198
|
+
|
|
1199
|
+
if (ctx.epicIdx < total) {
|
|
1200
|
+
lines.push(`๐ฏ Current: Epic ${currentEpic} โ ${subPhaseLabel} (round ${ctx.round + 1}/${ctx.maxRounds})`);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Per-epic summary
|
|
1204
|
+
if (tested > 0) {
|
|
1205
|
+
lines.push('', '๐ Results:');
|
|
1206
|
+
for (const epicId of ctx.epicIds) {
|
|
1207
|
+
const r = ctx.results[epicId];
|
|
1208
|
+
if (r && r.status === 'passed') {
|
|
1209
|
+
lines.push(` โ
Epic ${epicId} โ passed (${r.rounds} round(s))`);
|
|
1210
|
+
} else if (r && r.status === 'failed') {
|
|
1211
|
+
lines.push(` โ Epic ${epicId} โ failed (${r.rounds} rounds)`);
|
|
1212
|
+
} else if (epicId === currentEpic) {
|
|
1213
|
+
lines.push(` โณ Epic ${epicId} โ in progress`);
|
|
1214
|
+
} else {
|
|
1215
|
+
lines.push(` โฌ Epic ${epicId} โ pending`);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
if (startedAt) lines.push(`\nโฑ๏ธ Elapsed: ${startedAt}`);
|
|
1221
|
+
|
|
1222
|
+
return lines.join('\n');
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1157
1225
|
/**
|
|
1158
1226
|
* Complete test phase: aggregate results, update state files, send summary.
|
|
1159
1227
|
*/
|
|
@@ -1220,6 +1288,7 @@ class PhaseOrchestrator {
|
|
|
1220
1288
|
|
|
1221
1289
|
this._phase = null;
|
|
1222
1290
|
this._testContext = null;
|
|
1291
|
+
this._testStartedAt = null;
|
|
1223
1292
|
log.engine(`Test phase complete: ${passed}/${total} passed, ${failed} failed`);
|
|
1224
1293
|
this.onComplete('test', lines.join('\n'));
|
|
1225
1294
|
}
|
package/lib/gateway/router.js
CHANGED
|
@@ -99,25 +99,38 @@ class Router {
|
|
|
99
99
|
|
|
100
100
|
try {
|
|
101
101
|
const focus = yaml.load(fs.readFileSync(focusPath, 'utf8')) || {};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
const phaseNum = parseInt(focus.phase, 10);
|
|
103
|
+
|
|
104
|
+
if (phaseNum === 3) {
|
|
105
|
+
// Skip if dev phase already complete
|
|
106
|
+
const phase3Path = path.join(projectRoot, '.yuri', 'state', 'phase3.yaml');
|
|
107
|
+
if (fs.existsSync(phase3Path)) {
|
|
108
|
+
const phase3 = yaml.load(fs.readFileSync(phase3Path, 'utf8')) || {};
|
|
109
|
+
if (phase3.status === 'complete') return;
|
|
110
|
+
}
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
// Check tmux session is alive
|
|
113
|
+
const { execSync } = require('child_process');
|
|
114
|
+
const sessions = execSync('tmux list-sessions -F "#{session_name}" 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
115
|
+
if (!sessions.split('\n').some((s) => s.startsWith('orchestrix-'))) return;
|
|
115
116
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
const card = this._buildStatusCard(projectRoot, focus);
|
|
118
|
+
if (card) {
|
|
119
|
+
log.router('Auto-reporting dev progress');
|
|
120
|
+
this._sendProactive(card);
|
|
121
|
+
}
|
|
122
|
+
} else if (phaseNum === 4) {
|
|
123
|
+
// Skip if test phase already complete
|
|
124
|
+
const phase4Path = path.join(projectRoot, '.yuri', 'state', 'phase4.yaml');
|
|
125
|
+
if (!fs.existsSync(phase4Path)) return;
|
|
126
|
+
const phase4 = yaml.load(fs.readFileSync(phase4Path, 'utf8')) || {};
|
|
127
|
+
if (phase4.status === 'complete' || phase4.status === 'complete_with_failures') return;
|
|
128
|
+
|
|
129
|
+
const card = this._buildTestStatusCard(projectRoot);
|
|
130
|
+
if (card) {
|
|
131
|
+
log.router('Auto-reporting test progress');
|
|
132
|
+
this._sendProactive(card);
|
|
133
|
+
}
|
|
121
134
|
}
|
|
122
135
|
} catch { /* silent */ }
|
|
123
136
|
}, interval);
|
|
@@ -290,7 +303,7 @@ class Router {
|
|
|
290
303
|
async _handlePhaseCommand(phase, msg) {
|
|
291
304
|
const projectRoot = engine.resolveProjectRoot();
|
|
292
305
|
if (!projectRoot) {
|
|
293
|
-
return { text: 'โ No active project found.
|
|
306
|
+
return { text: 'โ No active project found. Run `/yuri *create` in your terminal first.' };
|
|
294
307
|
}
|
|
295
308
|
|
|
296
309
|
let response;
|
|
@@ -407,6 +420,16 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
|
|
|
407
420
|
}
|
|
408
421
|
}
|
|
409
422
|
|
|
423
|
+
// Test phase: generate test progress card from phase4.yaml
|
|
424
|
+
if (phaseNum === 4 && parts.length === 0) {
|
|
425
|
+
try {
|
|
426
|
+
const card = this._buildTestStatusCard(projectRoot);
|
|
427
|
+
if (card) parts.push(card);
|
|
428
|
+
} catch (err) {
|
|
429
|
+
log.warn(`Test progress card failed: ${err.message}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
410
433
|
// Fallback or non-dev phase
|
|
411
434
|
if (parts.length === 0) {
|
|
412
435
|
if (focus.pulse) parts.push(`Pulse: ${focus.pulse}`);
|
|
@@ -423,7 +446,7 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
|
|
|
423
446
|
}
|
|
424
447
|
|
|
425
448
|
if (parts.length === 0) {
|
|
426
|
-
parts.push('No active phase. Available commands: *
|
|
449
|
+
parts.push('No active phase. Available commands: *plan, *develop, *test, *deploy, *projects, *switch');
|
|
427
450
|
}
|
|
428
451
|
|
|
429
452
|
this.history.append(msg.chatId, 'user', msg.text);
|
|
@@ -609,13 +632,13 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
|
|
|
609
632
|
_handleProjects(msg) {
|
|
610
633
|
const registryPath = path.join(YURI_GLOBAL, 'portfolio', 'registry.yaml');
|
|
611
634
|
if (!fs.existsSync(registryPath)) {
|
|
612
|
-
return { text: 'No projects registered yet.
|
|
635
|
+
return { text: 'No projects registered yet. Run `/yuri *create` in your terminal first.' };
|
|
613
636
|
}
|
|
614
637
|
|
|
615
638
|
const registry = yaml.load(fs.readFileSync(registryPath, 'utf8')) || {};
|
|
616
639
|
const projects = registry.projects || [];
|
|
617
640
|
if (projects.length === 0) {
|
|
618
|
-
return { text: 'No projects registered yet.
|
|
641
|
+
return { text: 'No projects registered yet. Run `/yuri *create` in your terminal first.' };
|
|
619
642
|
}
|
|
620
643
|
|
|
621
644
|
// Read active project
|
|
@@ -824,6 +847,52 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
|
|
|
824
847
|
return lines.join('\n');
|
|
825
848
|
}
|
|
826
849
|
|
|
850
|
+
// โโ Test Progress Card (from phase4.yaml, for *status when orchestrator is not tracking) โโ
|
|
851
|
+
|
|
852
|
+
_buildTestStatusCard(projectRoot) {
|
|
853
|
+
const phase4Path = path.join(projectRoot, '.yuri', 'state', 'phase4.yaml');
|
|
854
|
+
if (!fs.existsSync(phase4Path)) return null;
|
|
855
|
+
|
|
856
|
+
const state = yaml.load(fs.readFileSync(phase4Path, 'utf8')) || {};
|
|
857
|
+
if (!Array.isArray(state.epics) || state.epics.length === 0) return null;
|
|
858
|
+
|
|
859
|
+
const total = state.epics.length;
|
|
860
|
+
const passed = state.epics.filter((e) => e.status === 'passed').length;
|
|
861
|
+
const failed = state.epics.filter((e) => e.status === 'failed').length;
|
|
862
|
+
const tested = passed + failed;
|
|
863
|
+
const pct = total > 0 ? Math.round((tested / total) * 100) : 0;
|
|
864
|
+
|
|
865
|
+
const barTotal = 20;
|
|
866
|
+
const filled = Math.round(pct / 100 * barTotal);
|
|
867
|
+
const bar = 'โ'.repeat(filled) + 'โ'.repeat(barTotal - filled) + ` ${pct}%`;
|
|
868
|
+
|
|
869
|
+
const lines = [
|
|
870
|
+
'๐งช **Test Phase Progress**',
|
|
871
|
+
'',
|
|
872
|
+
bar,
|
|
873
|
+
'',
|
|
874
|
+
`๐ Epics: ${tested}/${total} tested (โ
${passed} passed, โ ${failed} failed)`,
|
|
875
|
+
'',
|
|
876
|
+
'๐ Results:',
|
|
877
|
+
];
|
|
878
|
+
|
|
879
|
+
for (const epic of state.epics) {
|
|
880
|
+
if (epic.status === 'passed') {
|
|
881
|
+
lines.push(` โ
Epic ${epic.id} โ passed (${epic.rounds || 0} round(s))`);
|
|
882
|
+
} else if (epic.status === 'failed') {
|
|
883
|
+
lines.push(` โ Epic ${epic.id} โ failed (${epic.rounds || 0} rounds)`);
|
|
884
|
+
} else {
|
|
885
|
+
lines.push(` โฌ Epic ${epic.id} โ ${epic.status || 'pending'}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (state.status === 'complete' || state.status === 'complete_with_failures') {
|
|
890
|
+
lines.push(`\nโ
Testing finished at ${state.completed_at || 'unknown'}`);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return lines.join('\n');
|
|
894
|
+
}
|
|
895
|
+
|
|
827
896
|
// โโ Help โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
828
897
|
|
|
829
898
|
_buildHelpText() {
|
|
@@ -832,7 +901,6 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
|
|
|
832
901
|
**Project Lifecycle**
|
|
833
902
|
| Command | Description |
|
|
834
903
|
|---------|-------------|
|
|
835
|
-
| \`*create\` | Create a new project (interactive Q&A) |
|
|
836
904
|
| \`*plan\` | Start planning phase (6 agents sequentially, background) |
|
|
837
905
|
| \`*develop\` | Start development phase (4 agents with HANDOFF, background) |
|
|
838
906
|
| \`*test\` | Start smoke testing (QA per epic, auto fix-retest) |
|