opencastle 0.29.0 → 0.30.0
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/dist/cli/convoy/engine.js +1 -1
- package/dist/cli/convoy/engine.js.map +1 -1
- package/dist/cli/pipeline.d.ts +17 -0
- package/dist/cli/pipeline.d.ts.map +1 -1
- package/dist/cli/pipeline.js +238 -19
- package/dist/cli/pipeline.js.map +1 -1
- package/dist/cli/pipeline.test.d.ts +2 -0
- package/dist/cli/pipeline.test.d.ts.map +1 -0
- package/dist/cli/pipeline.test.js +178 -0
- package/dist/cli/pipeline.test.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/convoy/engine.ts +1 -1
- package/src/cli/pipeline.test.ts +191 -0
- package/src/cli/pipeline.ts +302 -21
- package/src/dashboard/dist/index.html +398 -5
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/src/pages/index.astro +490 -4
- package/src/orchestrator/agents/team-lead.agent.md +13 -0
- package/src/orchestrator/prompts/fix-prd.prompt.md +58 -0
- package/src/orchestrator/prompts/generate-convoy.prompt.md +23 -0
- package/src/orchestrator/prompts/generate-prd.prompt.md +32 -0
|
@@ -40,11 +40,18 @@ try {
|
|
|
40
40
|
<li><a class="dash-sidebar__link dash-sidebar__link--active" href="#overall-section" data-section="overall-section" data-view="home" aria-label="Overview section">Overview</a></li>
|
|
41
41
|
<li><a class="dash-sidebar__link" href="#convoy-list-section" data-section="convoy-list-section" data-view="home" aria-label="Convoys section">Convoys</a></li>
|
|
42
42
|
<li><a class="dash-sidebar__link" href="#tasks-section" data-section="tasks-section" data-view="detail" aria-label="Tasks section">Tasks</a></li>
|
|
43
|
+
<li><a class="dash-sidebar__link" href="#pipeline-section" data-section="pipeline-section" data-view="detail" aria-label="Pipeline section">Pipeline</a></li>
|
|
44
|
+
<li><a class="dash-sidebar__link" href="#agent-section" data-section="agent-section" data-view="detail" aria-label="Agents section">Agents</a></li>
|
|
45
|
+
<li><a class="dash-sidebar__link" href="#tier-section" data-section="tier-section" data-view="detail" aria-label="Tiers section">Tiers</a></li>
|
|
46
|
+
<li><a class="dash-sidebar__link" href="#model-section" data-section="model-section" data-view="detail" aria-label="Models section">Models</a></li>
|
|
43
47
|
<li><a class="dash-sidebar__link" href="#quality-section" data-section="quality-section" data-view="detail" aria-label="Quality section">Quality</a></li>
|
|
44
48
|
<li><a class="dash-sidebar__link" href="#reliability-section" data-section="reliability-section" data-view="detail" aria-label="Reliability section">Reliability</a></li>
|
|
45
49
|
<li><a class="dash-sidebar__link" href="#drift-section" data-section="drift-section" data-view="detail" aria-label="Drift section">Drift</a></li>
|
|
46
50
|
<li><a class="dash-sidebar__link" href="#outputs-section" data-section="outputs-section" data-view="detail" aria-label="Outputs section">Outputs</a></li>
|
|
51
|
+
<li><a class="dash-sidebar__link" href="#execution-section" data-section="execution-section" data-view="detail" aria-label="Execution Log section">Execution Log</a></li>
|
|
47
52
|
<li><a class="dash-sidebar__link" href="#event-timeline-section" data-section="event-timeline-section" data-view="detail" aria-label="Event Log section">Event Log</a></li>
|
|
53
|
+
<li><a class="dash-sidebar__link" href="#panel-section" data-section="panel-section" data-view="detail" aria-label="Panel Reviews section">Panel Reviews</a></li>
|
|
54
|
+
<li><a class="dash-sidebar__link" href="#reviews-section" data-section="reviews-section" data-view="detail" aria-label="Fast Reviews section">Fast Reviews</a></li>
|
|
48
55
|
</ul>
|
|
49
56
|
</nav>
|
|
50
57
|
|
|
@@ -152,6 +159,61 @@ try {
|
|
|
152
159
|
</div>
|
|
153
160
|
</section>
|
|
154
161
|
|
|
162
|
+
<!-- Pipeline View -->
|
|
163
|
+
<section class="chart-card" id="pipeline-section" data-nav-section>
|
|
164
|
+
<div class="chart-card__header">
|
|
165
|
+
<h2 class="chart-card__title">Task Pipeline</h2>
|
|
166
|
+
<span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Task flow across execution phases." data-tooltip="Task flow across execution phases."><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></span>
|
|
167
|
+
<p class="chart-card__desc">Task flow across execution phases</p>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="chart-card__body" id="pipeline-view"></div>
|
|
170
|
+
</section>
|
|
171
|
+
|
|
172
|
+
<!-- Charts Row: Agent + Outcomes -->
|
|
173
|
+
<div class="charts-row" id="agent-section" data-nav-section>
|
|
174
|
+
<section class="chart-card">
|
|
175
|
+
<div class="chart-card__header">
|
|
176
|
+
<h2 class="chart-card__title">Sessions by Agent</h2>
|
|
177
|
+
<p class="chart-card__desc">Task count per agent, stacked by outcome</p>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="chart-card__body" id="agent-chart"></div>
|
|
180
|
+
</section>
|
|
181
|
+
<section class="chart-card">
|
|
182
|
+
<div class="chart-card__header">
|
|
183
|
+
<h2 class="chart-card__title">Delegation Outcomes</h2>
|
|
184
|
+
<p class="chart-card__desc">Task outcome distribution</p>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="chart-card__body" id="delegation-outcome-chart"></div>
|
|
187
|
+
</section>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<!-- Charts Row: Tiers + Mechanism -->
|
|
191
|
+
<div class="charts-row" id="tier-section" data-nav-section>
|
|
192
|
+
<section class="chart-card">
|
|
193
|
+
<div class="chart-card__header">
|
|
194
|
+
<h2 class="chart-card__title">Tier Distribution</h2>
|
|
195
|
+
<p class="chart-card__desc">Model tier breakdown</p>
|
|
196
|
+
</div>
|
|
197
|
+
<div class="chart-card__body" id="tier-chart"></div>
|
|
198
|
+
</section>
|
|
199
|
+
<section class="chart-card">
|
|
200
|
+
<div class="chart-card__header">
|
|
201
|
+
<h2 class="chart-card__title">Delegation Mechanism</h2>
|
|
202
|
+
<p class="chart-card__desc">Sub-agent vs background split</p>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="chart-card__body" id="mechanism-chart"></div>
|
|
205
|
+
</section>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<!-- Model Usage -->
|
|
209
|
+
<section class="chart-card" id="model-section" data-nav-section>
|
|
210
|
+
<div class="chart-card__header">
|
|
211
|
+
<h2 class="chart-card__title">Model Usage</h2>
|
|
212
|
+
<p class="chart-card__desc">Tasks by model</p>
|
|
213
|
+
</div>
|
|
214
|
+
<div class="chart-card__body" id="model-chart"></div>
|
|
215
|
+
</section>
|
|
216
|
+
|
|
155
217
|
<!-- Quality Section -->
|
|
156
218
|
<section class="chart-card" id="quality-section" data-nav-section>
|
|
157
219
|
<div class="chart-card__header">
|
|
@@ -223,6 +285,37 @@ try {
|
|
|
223
285
|
</div>
|
|
224
286
|
</div>
|
|
225
287
|
</section>
|
|
288
|
+
|
|
289
|
+
<!-- Execution Log -->
|
|
290
|
+
<section class="chart-card" id="execution-section" data-nav-section>
|
|
291
|
+
<div class="chart-card__header">
|
|
292
|
+
<h2 class="chart-card__title">Execution Log</h2>
|
|
293
|
+
<span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Step-by-step trace of recent task activity." data-tooltip="Step-by-step trace of recent task activity."><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></span>
|
|
294
|
+
<p class="chart-card__desc">Recent agent activity, step by step</p>
|
|
295
|
+
</div>
|
|
296
|
+
<div class="chart-card__body" id="execution-log"></div>
|
|
297
|
+
</section>
|
|
298
|
+
|
|
299
|
+
<!-- Panel Reviews -->
|
|
300
|
+
<section class="chart-card" id="panel-section" data-nav-section>
|
|
301
|
+
<div class="chart-card__header">
|
|
302
|
+
<h2 class="chart-card__title">Panel Reviews</h2>
|
|
303
|
+
<span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Quality gate verdicts from majority-vote panels." data-tooltip="Quality gate verdicts from majority-vote panels."><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></span>
|
|
304
|
+
<p class="chart-card__desc">Quality gate verdicts and fix items</p>
|
|
305
|
+
</div>
|
|
306
|
+
<div class="chart-card__body" id="panel-chart"></div>
|
|
307
|
+
</section>
|
|
308
|
+
|
|
309
|
+
<!-- Fast Reviews -->
|
|
310
|
+
<section class="chart-card" id="reviews-section" data-nav-section>
|
|
311
|
+
<div class="chart-card__header">
|
|
312
|
+
<h2 class="chart-card__title">Fast Reviews</h2>
|
|
313
|
+
<span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Single-reviewer quality gate results." data-tooltip="Single-reviewer quality gate results."><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg></span>
|
|
314
|
+
<p class="chart-card__desc">Single-reviewer quality gate results</p>
|
|
315
|
+
</div>
|
|
316
|
+
<div class="chart-card__body chart-card__body--table" id="reviews-table"></div>
|
|
317
|
+
</section>
|
|
318
|
+
|
|
226
319
|
</div><!-- .view-convoy-detail -->
|
|
227
320
|
|
|
228
321
|
|
|
@@ -539,7 +632,7 @@ try {
|
|
|
539
632
|
async function loadConvoyDetail(convoyId) {
|
|
540
633
|
if (!convoyId) {
|
|
541
634
|
renderConvoyDetailHeader(null);
|
|
542
|
-
['quality-section', 'reliability-section', 'drift-section', 'outputs-section', 'event-timeline-section'].forEach(function(id) {
|
|
635
|
+
['pipeline-section', 'agent-section', 'tier-section', 'model-section', 'quality-section', 'reliability-section', 'drift-section', 'outputs-section', 'execution-section', 'event-timeline-section', 'panel-section', 'reviews-section'].forEach(function(id) {
|
|
543
636
|
var el = document.getElementById(id);
|
|
544
637
|
if (el) el.style.display = 'none';
|
|
545
638
|
});
|
|
@@ -556,15 +649,24 @@ try {
|
|
|
556
649
|
renderPhaseBreakdown(detail.tasks || []);
|
|
557
650
|
const tasksSection = document.getElementById('tasks-section');
|
|
558
651
|
if (tasksSection) tasksSection.style.display = '';
|
|
652
|
+
renderDetailPipeline(detail.tasks || []);
|
|
653
|
+
renderDetailAgentChart(detail.tasks || []);
|
|
654
|
+
renderDetailOutcomeChart(detail.tasks || []);
|
|
655
|
+
renderDetailTierChart(detail.tasks || []);
|
|
656
|
+
renderDetailMechanismChart(detail.events || []);
|
|
657
|
+
renderDetailModelChart(detail.tasks || []);
|
|
559
658
|
renderQualitySection(detail);
|
|
560
659
|
renderReliabilitySection(detail);
|
|
561
660
|
renderDriftSection(detail);
|
|
562
661
|
renderOutputsSection(detail);
|
|
662
|
+
renderDetailExecutionLog(detail.tasks || []);
|
|
563
663
|
renderEventTimeline(detail);
|
|
664
|
+
renderDetailPanelChart(detail.tasks || []);
|
|
665
|
+
renderDetailReviewsTable(detail.tasks || []);
|
|
564
666
|
} catch (e) {
|
|
565
667
|
console.error('Failed to load convoy detail:', e);
|
|
566
668
|
renderConvoyDetailHeader(null);
|
|
567
|
-
['quality-section', 'reliability-section', 'drift-section', 'outputs-section', 'event-timeline-section'].forEach(function(id) {
|
|
669
|
+
['pipeline-section', 'agent-section', 'tier-section', 'model-section', 'quality-section', 'reliability-section', 'drift-section', 'outputs-section', 'execution-section', 'event-timeline-section', 'panel-section', 'reviews-section'].forEach(function(id) {
|
|
568
670
|
var el = document.getElementById(id);
|
|
569
671
|
if (el) el.style.display = 'none';
|
|
570
672
|
});
|
|
@@ -580,7 +682,7 @@ try {
|
|
|
580
682
|
if (nameEl) nameEl.textContent = 'No convoy selected';
|
|
581
683
|
if (statusEl) { statusEl.textContent = ''; statusEl.className = 'status-badge'; }
|
|
582
684
|
if (metaEl) metaEl.innerHTML = '';
|
|
583
|
-
['tasks-section', 'quality-section', 'reliability-section', 'drift-section', 'outputs-section', 'event-timeline-section'].forEach(function(id) {
|
|
685
|
+
['tasks-section', 'pipeline-section', 'agent-section', 'tier-section', 'model-section', 'quality-section', 'reliability-section', 'drift-section', 'outputs-section', 'execution-section', 'event-timeline-section', 'panel-section', 'reviews-section'].forEach(function(id) {
|
|
584
686
|
var el = document.getElementById(id);
|
|
585
687
|
if (el) el.style.display = 'none';
|
|
586
688
|
});
|
|
@@ -778,6 +880,390 @@ try {
|
|
|
778
880
|
el.innerHTML = html;
|
|
779
881
|
}
|
|
780
882
|
|
|
883
|
+
// ── Convoy Detail: Derive Tier from Model ────────────────
|
|
884
|
+
|
|
885
|
+
function deriveTier(model) {
|
|
886
|
+
if (!model) return 'unknown';
|
|
887
|
+
var m = model.toLowerCase();
|
|
888
|
+
if (m.includes('opus')) return 'premium';
|
|
889
|
+
if (m.includes('sonnet') || m.includes('pro')) return 'standard';
|
|
890
|
+
if (m.includes('haiku') || m.includes('flash') || m.includes('mini')) return 'economy';
|
|
891
|
+
return 'utility';
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// ── Convoy Detail: Pipeline View ─────────────────────────
|
|
895
|
+
|
|
896
|
+
function renderDetailPipeline(tasks) {
|
|
897
|
+
var el = document.getElementById('pipeline-view');
|
|
898
|
+
if (!el) return;
|
|
899
|
+
|
|
900
|
+
if (!tasks || tasks.length === 0) {
|
|
901
|
+
el.innerHTML = emptyStateHtml('pipeline', 'No pipeline activity yet', 'Tasks will flow through execution phases here.');
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
var phases = {};
|
|
906
|
+
tasks.forEach(function(t) {
|
|
907
|
+
var p = t.phase != null ? t.phase : 1;
|
|
908
|
+
if (!phases[p]) phases[p] = 0;
|
|
909
|
+
phases[p]++;
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
var stageConfig = [
|
|
913
|
+
{ label: 'Foundation', phase: 1, iconClass: 'pending', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/><line x1="8" y1="10" x2="16" y2="10"/><line x1="8" y1="14" x2="13" y2="14"/></svg>' },
|
|
914
|
+
{ label: 'Integration', phase: 2, iconClass: 'active', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>' },
|
|
915
|
+
{ label: 'Validation', phase: 3, iconClass: 'review', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>' },
|
|
916
|
+
{ label: 'QA Gate', phase: 4, iconClass: 'done', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>' },
|
|
917
|
+
];
|
|
918
|
+
|
|
919
|
+
el.innerHTML =
|
|
920
|
+
'<div class="pipeline">' +
|
|
921
|
+
stageConfig.map(function(stage, i) {
|
|
922
|
+
return (i > 0 ? '<div class="pipeline-arrow">\u2192</div>' : '') +
|
|
923
|
+
'<div class="pipeline-stage">' +
|
|
924
|
+
'<div class="pipeline-stage__icon pipeline-stage__icon--' + stage.iconClass + '">' + stage.icon + '</div>' +
|
|
925
|
+
'<span class="pipeline-stage__count">' + (phases[stage.phase] || 0) + '</span>' +
|
|
926
|
+
'<span class="pipeline-stage__label">' + stage.label + '</span>' +
|
|
927
|
+
'</div>';
|
|
928
|
+
}).join('') +
|
|
929
|
+
'</div>';
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// ── Convoy Detail: Agent Chart ───────────────────────────
|
|
933
|
+
|
|
934
|
+
function renderDetailAgentChart(tasks) {
|
|
935
|
+
var el = document.getElementById('agent-chart');
|
|
936
|
+
if (!el) return;
|
|
937
|
+
|
|
938
|
+
if (!tasks || tasks.length === 0) {
|
|
939
|
+
el.innerHTML = emptyStateHtml('agents', 'No agent data yet', 'A breakdown of tasks per agent will appear here.');
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
var agentMap = {};
|
|
944
|
+
tasks.forEach(function(t) {
|
|
945
|
+
var agent = t.agent || 'unknown';
|
|
946
|
+
if (!agentMap[agent]) agentMap[agent] = { done: 0, failed: 0, running: 0, other: 0, total: 0 };
|
|
947
|
+
if (t.status === 'done') agentMap[agent].done++;
|
|
948
|
+
else if (t.status === 'running') agentMap[agent].running++;
|
|
949
|
+
else if (['failed', 'gate-failed', 'timed-out', 'hook-failed'].includes(t.status)) agentMap[agent].failed++;
|
|
950
|
+
else agentMap[agent].other++;
|
|
951
|
+
agentMap[agent].total++;
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
var agents = Object.entries(agentMap).sort(function(a, b) { return b[1].total - a[1].total; });
|
|
955
|
+
var maxTotal = Math.max.apply(null, agents.map(function(a) { return a[1].total; }));
|
|
956
|
+
|
|
957
|
+
el.innerHTML = agents.map(function(entry) {
|
|
958
|
+
var name = entry[0];
|
|
959
|
+
var data = entry[1];
|
|
960
|
+
return '<div class="bar-row">' +
|
|
961
|
+
'<span class="bar-label">' + escapeHtml(name) + '</span>' +
|
|
962
|
+
'<div class="bar-track">' +
|
|
963
|
+
(data.done > 0 ? '<div class="bar-segment bar--success" style="width: ' + ((data.done / maxTotal) * 100).toFixed(1) + '%"></div>' : '') +
|
|
964
|
+
(data.running > 0 ? '<div class="bar-segment" style="width: ' + ((data.running / maxTotal) * 100).toFixed(1) + '%; background: #3b82f6"></div>' : '') +
|
|
965
|
+
(data.failed > 0 ? '<div class="bar-segment bar--failed" style="width: ' + ((data.failed / maxTotal) * 100).toFixed(1) + '%"></div>' : '') +
|
|
966
|
+
(data.other > 0 ? '<div class="bar-segment" style="width: ' + ((data.other / maxTotal) * 100).toFixed(1) + '%; background: #64748b"></div>' : '') +
|
|
967
|
+
'</div>' +
|
|
968
|
+
'<span class="bar-value">' + data.total + '</span>' +
|
|
969
|
+
'</div>';
|
|
970
|
+
}).join('');
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// ── Convoy Detail: Tier Donut Chart ──────────────────────
|
|
974
|
+
|
|
975
|
+
function renderDetailTierChart(tasks) {
|
|
976
|
+
var el = document.getElementById('tier-chart');
|
|
977
|
+
if (!el) return;
|
|
978
|
+
|
|
979
|
+
if (!tasks || tasks.length === 0) {
|
|
980
|
+
el.innerHTML = emptyStateHtml('tiers', 'No tier data yet', 'Model tier distribution will be shown as a donut chart.');
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
var tierCounts = {};
|
|
985
|
+
tasks.forEach(function(t) {
|
|
986
|
+
var tier = deriveTier(t.model);
|
|
987
|
+
tierCounts[tier] = (tierCounts[tier] || 0) + 1;
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
var order = ['premium', 'standard', 'utility', 'economy', 'unknown'];
|
|
991
|
+
var tiers = order.filter(function(t) { return tierCounts[t]; }).map(function(t) { return { name: t, count: tierCounts[t] }; });
|
|
992
|
+
var total = tasks.length;
|
|
993
|
+
var r = 70;
|
|
994
|
+
var circumference = 2 * Math.PI * r;
|
|
995
|
+
var cumOffset = 0;
|
|
996
|
+
|
|
997
|
+
var circles = tiers.map(function(t) {
|
|
998
|
+
var pct = t.count / total;
|
|
999
|
+
var dashLen = pct * circumference;
|
|
1000
|
+
var linecap = tiers.length === 1 ? 'butt' : 'round';
|
|
1001
|
+
var segment = '<circle cx="90" cy="90" r="' + r + '" fill="none" stroke="' + (TIER_COLORS[t.name] || '#64748b') + '" stroke-width="18" stroke-dasharray="' + dashLen.toFixed(2) + ' ' + (circumference - dashLen).toFixed(2) + '" stroke-dashoffset="' + (-cumOffset).toFixed(2) + '" transform="rotate(-90 90 90)" stroke-linecap="' + linecap + '"/>';
|
|
1002
|
+
cumOffset += dashLen;
|
|
1003
|
+
return segment;
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
var legend = tiers.map(function(t) {
|
|
1007
|
+
return '<div class="legend-item"><span class="legend-dot" style="background: ' + (TIER_COLORS[t.name] || '#64748b') + '"></span><span class="legend-name">' + t.name + '</span><span class="legend-count">' + t.count + ' (' + Math.round((t.count / total) * 100) + '%)</span></div>';
|
|
1008
|
+
}).join('');
|
|
1009
|
+
|
|
1010
|
+
el.innerHTML =
|
|
1011
|
+
'<div class="donut-container"><div class="donut-wrap"><svg viewBox="0 0 180 180" class="donut-svg">' + circles.join('') + '</svg><div class="donut-center"><span class="donut-total">' + total + '</span><span class="donut-total-label">total</span></div></div><div class="donut-legend">' + legend + '</div></div>';
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// ── Convoy Detail: Mechanism Donut Chart ─────────────────
|
|
1015
|
+
|
|
1016
|
+
function renderDetailMechanismChart(events) {
|
|
1017
|
+
var el = document.getElementById('mechanism-chart');
|
|
1018
|
+
if (!el) return;
|
|
1019
|
+
|
|
1020
|
+
var MECH_COLORS = { 'sub-agent': '#3b82f6', 'background': '#a78bfa', 'unknown': '#64748b' };
|
|
1021
|
+
var MECH_LABELS = { 'sub-agent': 'Sub-agent (inline)', 'background': 'Background (worktree)', 'unknown': 'Unknown' };
|
|
1022
|
+
|
|
1023
|
+
if (!events || events.length === 0) {
|
|
1024
|
+
el.innerHTML = emptyStateHtml('mechanism', 'No delegation data yet', 'The split between sub-agent and background delegations will be shown here.');
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
var mechCounts = {};
|
|
1029
|
+
events.forEach(function(e) {
|
|
1030
|
+
if (e.type === 'task_assigned' || e.type === 'task_started') {
|
|
1031
|
+
var mech = (e.data && e.data.mechanism) || 'unknown';
|
|
1032
|
+
mechCounts[mech] = (mechCounts[mech] || 0) + 1;
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
var mechOrder = ['sub-agent', 'background', 'unknown'];
|
|
1037
|
+
var mechs = mechOrder.filter(function(m) { return mechCounts[m]; }).map(function(m) { return { name: m, count: mechCounts[m] }; });
|
|
1038
|
+
|
|
1039
|
+
if (mechs.length === 0) {
|
|
1040
|
+
el.innerHTML = emptyStateHtml('mechanism', 'No delegation data yet', 'The split between sub-agent and background delegations will be shown here.');
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
var total = mechs.reduce(function(s, m) { return s + m.count; }, 0);
|
|
1045
|
+
var r = 70;
|
|
1046
|
+
var circumference = 2 * Math.PI * r;
|
|
1047
|
+
var cumOffset = 0;
|
|
1048
|
+
|
|
1049
|
+
var circles = mechs.map(function(m) {
|
|
1050
|
+
var pct = m.count / total;
|
|
1051
|
+
var dashLen = pct * circumference;
|
|
1052
|
+
var linecap = mechs.length === 1 ? 'butt' : 'round';
|
|
1053
|
+
var segment = '<circle cx="90" cy="90" r="' + r + '" fill="none" stroke="' + (MECH_COLORS[m.name] || '#64748b') + '" stroke-width="18" stroke-dasharray="' + dashLen.toFixed(2) + ' ' + (circumference - dashLen).toFixed(2) + '" stroke-dashoffset="' + (-cumOffset).toFixed(2) + '" transform="rotate(-90 90 90)" stroke-linecap="' + linecap + '"/>';
|
|
1054
|
+
cumOffset += dashLen;
|
|
1055
|
+
return segment;
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
var legend = mechs.map(function(m) {
|
|
1059
|
+
return '<div class="legend-item"><span class="legend-dot" style="background: ' + (MECH_COLORS[m.name] || '#64748b') + '"></span><span class="legend-name">' + (MECH_LABELS[m.name] || m.name) + '</span><span class="legend-count">' + m.count + ' (' + Math.round((m.count / total) * 100) + '%)</span></div>';
|
|
1060
|
+
}).join('');
|
|
1061
|
+
|
|
1062
|
+
el.innerHTML =
|
|
1063
|
+
'<div class="donut-container"><div class="donut-wrap"><svg viewBox="0 0 180 180" class="donut-svg">' + circles.join('') + '</svg><div class="donut-center"><span class="donut-total">' + total + '</span><span class="donut-total-label">total</span></div></div><div class="donut-legend">' + legend + '</div></div>';
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// ── Convoy Detail: Delegation Outcome Chart ──────────────
|
|
1067
|
+
|
|
1068
|
+
function renderDetailOutcomeChart(tasks) {
|
|
1069
|
+
var el = document.getElementById('delegation-outcome-chart');
|
|
1070
|
+
if (!el) return;
|
|
1071
|
+
|
|
1072
|
+
if (!tasks || tasks.length === 0) {
|
|
1073
|
+
el.innerHTML = emptyStateHtml('outcomes', 'No outcome data yet', 'Task outcome distribution will be shown here.');
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
var OUTCOME_COLORS = { done: '#22c55e', running: '#3b82f6', pending: '#64748b', assigned: '#64748b', failed: '#ef4444', 'gate-failed': '#f59e0b', 'timed-out': '#a78bfa', 'hook-failed': '#64748b', 'review-blocked': '#f59e0b', skipped: '#94a3b8' };
|
|
1078
|
+
|
|
1079
|
+
var outcomeCounts = {};
|
|
1080
|
+
tasks.forEach(function(t) {
|
|
1081
|
+
var outcome = t.status || 'unknown';
|
|
1082
|
+
outcomeCounts[outcome] = (outcomeCounts[outcome] || 0) + 1;
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
var outcomes = Object.entries(outcomeCounts).sort(function(a, b) { return b[1] - a[1]; });
|
|
1086
|
+
var maxCount = Math.max.apply(null, outcomes.map(function(o) { return o[1]; }));
|
|
1087
|
+
|
|
1088
|
+
el.innerHTML = outcomes.map(function(entry) {
|
|
1089
|
+
var name = entry[0];
|
|
1090
|
+
var count = entry[1];
|
|
1091
|
+
return '<div class="bar-row">' +
|
|
1092
|
+
'<span class="bar-label">' + escapeHtml(name) + '</span>' +
|
|
1093
|
+
'<div class="bar-track"><div class="bar-segment" style="width: ' + ((count / maxCount) * 100).toFixed(1) + '%; background: ' + (OUTCOME_COLORS[name] || '#64748b') + '"></div></div>' +
|
|
1094
|
+
'<span class="bar-value">' + count + '</span>' +
|
|
1095
|
+
'</div>';
|
|
1096
|
+
}).join('');
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// ── Convoy Detail: Model Chart ───────────────────────────
|
|
1100
|
+
|
|
1101
|
+
function renderDetailModelChart(tasks) {
|
|
1102
|
+
var el = document.getElementById('model-chart');
|
|
1103
|
+
if (!el) return;
|
|
1104
|
+
|
|
1105
|
+
if (!tasks || tasks.length === 0) {
|
|
1106
|
+
el.innerHTML = emptyStateHtml('models', 'No model data yet', 'Model utilization across tasks will be shown here.');
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
var modelCounts = {};
|
|
1111
|
+
tasks.forEach(function(t) {
|
|
1112
|
+
if (t.model) modelCounts[t.model] = (modelCounts[t.model] || 0) + 1;
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
var models = Object.entries(modelCounts).sort(function(a, b) { return b[1] - a[1]; });
|
|
1116
|
+
|
|
1117
|
+
if (models.length === 0) {
|
|
1118
|
+
el.innerHTML = emptyStateHtml('models', 'No model data yet', 'Model utilization across tasks will be shown here.');
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
var maxCount = Math.max.apply(null, models.map(function(m) { return m[1]; }));
|
|
1123
|
+
|
|
1124
|
+
el.innerHTML = models.map(function(entry) {
|
|
1125
|
+
var name = entry[0];
|
|
1126
|
+
var count = entry[1];
|
|
1127
|
+
return '<div class="bar-row">' +
|
|
1128
|
+
'<span class="bar-label">' + escapeHtml(name) + '</span>' +
|
|
1129
|
+
'<div class="bar-track"><div class="bar-segment" style="width: ' + ((count / maxCount) * 100).toFixed(1) + '%; background: ' + (MODEL_COLORS[name] || '#64748b') + '"></div></div>' +
|
|
1130
|
+
'<span class="bar-value">' + count + '</span>' +
|
|
1131
|
+
'</div>';
|
|
1132
|
+
}).join('');
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// ── Convoy Detail: Execution Log ─────────────────────────
|
|
1136
|
+
|
|
1137
|
+
function renderDetailExecutionLog(tasks) {
|
|
1138
|
+
var el = document.getElementById('execution-log');
|
|
1139
|
+
if (!el) return;
|
|
1140
|
+
|
|
1141
|
+
if (!tasks || tasks.length === 0) {
|
|
1142
|
+
el.innerHTML = emptyStateHtml('execLog', 'No execution history yet', 'A step-by-step trace of task activity will appear here.');
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
var sorted = tasks.slice().filter(function(t) { return t.started_at; }).sort(function(a, b) { return new Date(b.started_at) - new Date(a.started_at); }).slice(0, 10);
|
|
1147
|
+
|
|
1148
|
+
if (sorted.length === 0) {
|
|
1149
|
+
el.innerHTML = emptyStateHtml('execLog', 'No execution history yet', 'A step-by-step trace of task activity will appear here.');
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
var statusToOutcome = function(s) {
|
|
1154
|
+
if (s === 'done') return 'success';
|
|
1155
|
+
if (['failed', 'gate-failed', 'timed-out', 'hook-failed'].includes(s)) return 'failed';
|
|
1156
|
+
return 'partial';
|
|
1157
|
+
};
|
|
1158
|
+
|
|
1159
|
+
el.innerHTML =
|
|
1160
|
+
'<div class="exec-log">' +
|
|
1161
|
+
sorted.map(function(t, i) {
|
|
1162
|
+
var outcome = statusToOutcome(t.status);
|
|
1163
|
+
var dur = formatDuration(t.started_at, t.finished_at);
|
|
1164
|
+
var fileCount = t.files ? t.files.length : 0;
|
|
1165
|
+
return '<div class="exec-step">' +
|
|
1166
|
+
'<div class="exec-step__indicator">' +
|
|
1167
|
+
'<div class="exec-step__dot exec-step__dot--' + outcome + '">' + (OUTCOME_ICONS[outcome] || '') + '</div>' +
|
|
1168
|
+
(i < sorted.length - 1 ? '<div class="exec-step__line"></div>' : '') +
|
|
1169
|
+
'</div>' +
|
|
1170
|
+
'<div class="exec-step__content">' +
|
|
1171
|
+
'<div class="exec-step__header">' +
|
|
1172
|
+
'<span class="exec-step__agent">' + escapeHtml(t.agent || 'unknown') + '</span>' +
|
|
1173
|
+
'<span class="exec-step__badge exec-step__badge--' + outcome + '">' + escapeHtml(t.status) + '</span>' +
|
|
1174
|
+
'</div>' +
|
|
1175
|
+
'<div class="exec-step__task">' + escapeHtml(t.id) + '</div>' +
|
|
1176
|
+
'<div class="exec-step__meta">' +
|
|
1177
|
+
'<span class="exec-step__meta-item">\uD83D\uDD52 ' + formatTime(t.started_at) + '</span>' +
|
|
1178
|
+
(dur ? '<span class="exec-step__meta-item">\u23F1 ' + dur + '</span>' : '') +
|
|
1179
|
+
(fileCount > 0 ? '<span class="exec-step__meta-item">\uD83D\uDCC1 ' + fileCount + ' files</span>' : '') +
|
|
1180
|
+
(t.model ? '<span class="exec-step__meta-item">\uD83E\uDD16 ' + escapeHtml(t.model) + '</span>' : '') +
|
|
1181
|
+
(t.retries > 0 ? '<span class="exec-step__meta-item">\uD83D\uDD04 ' + t.retries + ' retries</span>' : '') +
|
|
1182
|
+
(t.total_tokens != null ? '<span class="exec-step__meta-item">\uD83D\uDD24 ' + formatTokens(t.total_tokens) + '</span>' : '') +
|
|
1183
|
+
'</div>' +
|
|
1184
|
+
'</div>' +
|
|
1185
|
+
'</div>';
|
|
1186
|
+
}).join('') +
|
|
1187
|
+
'</div>';
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// ── Convoy Detail: Panel Reviews ─────────────────────────
|
|
1191
|
+
|
|
1192
|
+
function renderDetailPanelChart(tasks) {
|
|
1193
|
+
var el = document.getElementById('panel-chart');
|
|
1194
|
+
if (!el) return;
|
|
1195
|
+
|
|
1196
|
+
var panelTasks = (tasks || []).filter(function(t) { return t.panel_attempts > 0; });
|
|
1197
|
+
|
|
1198
|
+
if (panelTasks.length === 0) {
|
|
1199
|
+
el.innerHTML = emptyStateHtml('panels', 'No panel reviews yet', 'Quality gate verdicts from majority-vote panels will be shown here.');
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
el.innerHTML =
|
|
1204
|
+
'<div class="panel-grid">' +
|
|
1205
|
+
panelTasks.map(function(t) {
|
|
1206
|
+
var verdictUpper = t.review_verdict ? t.review_verdict.toUpperCase() : 'PENDING';
|
|
1207
|
+
var verdictClass = verdictUpper === 'PASS' ? 'PASS' : 'BLOCK';
|
|
1208
|
+
return '<div class="panel-item">' +
|
|
1209
|
+
'<div class="panel-item__header">' +
|
|
1210
|
+
'<span class="panel-item__key">' + escapeHtml(t.id) + '</span>' +
|
|
1211
|
+
'<span class="panel-item__verdict panel-item__verdict--' + verdictClass + '">' + verdictUpper + '</span>' +
|
|
1212
|
+
'</div>' +
|
|
1213
|
+
'<div class="panel-item__votes">' +
|
|
1214
|
+
(verdictUpper === 'PASS' ? '<div class="panel-item__vote panel-item__vote--pass">\u2713</div>' : '<div class="panel-item__vote panel-item__vote--block">\u2717</div>') +
|
|
1215
|
+
'</div>' +
|
|
1216
|
+
'<div class="panel-item__meta">' +
|
|
1217
|
+
'<span class="panel-item__meta-item">\uD83E\uDD16 ' + escapeHtml(t.review_model || 'unknown') + '</span>' +
|
|
1218
|
+
(t.panel_attempts > 1 ? '<span class="panel-item__meta-item">\uD83D\uDD04 ' + t.panel_attempts + ' attempts</span>' : '') +
|
|
1219
|
+
(t.review_tokens != null ? '<span class="panel-item__meta-item">\uD83D\uDD24 ' + formatTokens(t.review_tokens) + '</span>' : '') +
|
|
1220
|
+
'</div>' +
|
|
1221
|
+
'</div>';
|
|
1222
|
+
}).join('') +
|
|
1223
|
+
'</div>';
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// ── Convoy Detail: Fast Reviews ──────────────────────────
|
|
1227
|
+
|
|
1228
|
+
function renderDetailReviewsTable(tasks) {
|
|
1229
|
+
var el = document.getElementById('reviews-table');
|
|
1230
|
+
if (!el) return;
|
|
1231
|
+
|
|
1232
|
+
var reviewedTasks = (tasks || []).filter(function(t) { return t.review_level != null; });
|
|
1233
|
+
|
|
1234
|
+
if (reviewedTasks.length === 0) {
|
|
1235
|
+
el.innerHTML = emptyStateHtml('panels', 'No fast reviews yet', 'Single-reviewer quality gate results will be listed here.');
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
el.innerHTML =
|
|
1240
|
+
'<table class="sessions-table">' +
|
|
1241
|
+
'<thead><tr>' +
|
|
1242
|
+
'<th scope="col">Task ID</th>' +
|
|
1243
|
+
'<th scope="col">Agent</th>' +
|
|
1244
|
+
'<th scope="col">Review Level</th>' +
|
|
1245
|
+
'<th scope="col">Verdict</th>' +
|
|
1246
|
+
'<th scope="col">Model</th>' +
|
|
1247
|
+
'<th scope="col">Tokens</th>' +
|
|
1248
|
+
'</tr></thead>' +
|
|
1249
|
+
'<tbody>' +
|
|
1250
|
+
reviewedTasks.map(function(t) {
|
|
1251
|
+
var verdictUpper = t.review_verdict ? t.review_verdict.toUpperCase() : '';
|
|
1252
|
+
var verdictBadge = t.review_verdict
|
|
1253
|
+
? '<span class="status-badge status-badge--' + (verdictUpper === 'PASS' ? 'done' : 'failed') + '">' + escapeHtml(t.review_verdict) + '</span>'
|
|
1254
|
+
: '<span style="opacity:0.4">\u2014</span>';
|
|
1255
|
+
return '<tr>' +
|
|
1256
|
+
'<td class="td-task">' + escapeHtml(t.id || '\u2014') + '</td>' +
|
|
1257
|
+
'<td class="td-agent">' + escapeHtml(t.agent || '\u2014') + '</td>' +
|
|
1258
|
+
'<td>' + escapeHtml(t.review_level || '\u2014') + '</td>' +
|
|
1259
|
+
'<td>' + verdictBadge + '</td>' +
|
|
1260
|
+
'<td>' + escapeHtml(t.review_model || '\u2014') + '</td>' +
|
|
1261
|
+
'<td class="td-num">' + (t.review_tokens != null ? formatTokens(t.review_tokens) : '\u2014') + '</td>' +
|
|
1262
|
+
'</tr>';
|
|
1263
|
+
}).join('') +
|
|
1264
|
+
'</tbody></table>';
|
|
1265
|
+
}
|
|
1266
|
+
|
|
781
1267
|
// ── Quality Section ──────────────────────────────────────
|
|
782
1268
|
|
|
783
1269
|
function renderQualitySection(detail) {
|
|
@@ -1227,7 +1713,7 @@ try {
|
|
|
1227
1713
|
|
|
1228
1714
|
sections.forEach((s) => observer.observe(s));
|
|
1229
1715
|
|
|
1230
|
-
const CONVOY_DETAIL_SECTIONS = ["tasks-section", "quality-section", "reliability-section", "drift-section", "outputs-section", "event-timeline-section"];
|
|
1716
|
+
const CONVOY_DETAIL_SECTIONS = ["tasks-section", "pipeline-section", "agent-section", "tier-section", "model-section", "quality-section", "reliability-section", "drift-section", "outputs-section", "execution-section", "event-timeline-section", "panel-section", "reviews-section"];
|
|
1231
1717
|
|
|
1232
1718
|
links.forEach((link) => {
|
|
1233
1719
|
link.addEventListener("click", async (e) => {
|
|
@@ -109,6 +109,19 @@ When calling `runSubagent`, always specify which custom agent to use by name: *"
|
|
|
109
109
|
|
|
110
110
|
**After each sub-agent returns**, log the delegation record before doing anything else (before review, before verification). This is a **⛔ hard gate** — do NOT proceed to review or any other action until the delegation is logged. Use the **observability-logging** skill's delegation record command (`--mechanism sub-agent`).
|
|
111
111
|
|
|
112
|
+
### Empty Output Handling
|
|
113
|
+
|
|
114
|
+
If a sub-agent returns empty, minimal, or off-topic output:
|
|
115
|
+
|
|
116
|
+
1. **Never fall back to writing content yourself** — Rule #1 still applies
|
|
117
|
+
2. **Retry with an explicit prompt** — Restate the objective with:
|
|
118
|
+
- Exact deliverables expected (e.g., "Return the full revised text, not a summary")
|
|
119
|
+
- The Output Contract from the agent's definition (paste it into the prompt)
|
|
120
|
+
- An example of what good output looks like
|
|
121
|
+
3. **Escalate the model** — If the Economy-tier agent fails twice, re-delegate to a Standard-tier agent (e.g., use Developer or UI/UX Expert for content tasks that require codebase context)
|
|
122
|
+
4. **Log the failure** — Even if retry succeeds, log the empty-output attempt as a delegation with `outcome: failed` and `failure_reason: empty_output`
|
|
123
|
+
5. **Max 3 attempts** — After 3 empty returns → DLQ the task to `.opencastle/AGENT-FAILURES.md`
|
|
124
|
+
|
|
112
125
|
> **`model` and `tier` must come from the agent registry** — not the Team Lead's own model. Look up the agent in [agent-registry.md](../.opencastle/agents/agent-registry.md) and use their assigned model and tier. For example, delegating to Developer → `"model":"claude-sonnet-4-6","tier":"quality"`, not the Team Lead's `claude-opus-4-6`.
|
|
113
126
|
|
|
114
127
|
### Background Agents — Delegate Session
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'Fix validation errors in a PRD. Goal is the broken PRD markdown; context is the error list from the validation step.'
|
|
3
|
+
agent: 'Team Lead (OpenCastle)'
|
|
4
|
+
output: prd
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .opencastle/ directory instead. -->
|
|
8
|
+
|
|
9
|
+
# Fix PRD
|
|
10
|
+
|
|
11
|
+
You are the Team Lead. The PRD below failed validation. Fix **every reported issue** and output a complete, corrected PRD.
|
|
12
|
+
|
|
13
|
+
## Failing PRD
|
|
14
|
+
|
|
15
|
+
{{goal}}
|
|
16
|
+
|
|
17
|
+
## Validation Errors
|
|
18
|
+
|
|
19
|
+
{{context}}
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Fix Instructions
|
|
24
|
+
|
|
25
|
+
1. Read every reported issue before making changes.
|
|
26
|
+
2. Fix **all** reported issues — do not partially fix.
|
|
27
|
+
3. Do not change the intent, goals, or scope of the feature. Only fix what the validator flagged.
|
|
28
|
+
4. Preserve all content that is not part of the reported issues.
|
|
29
|
+
|
|
30
|
+
### Common Fix Patterns
|
|
31
|
+
|
|
32
|
+
**Missing sections**
|
|
33
|
+
- Add the missing section with concrete, specific content — not placeholder text
|
|
34
|
+
- If the section needs real data you cannot infer, write a reasonable default and mark with `<!-- TODO: verify -->`
|
|
35
|
+
|
|
36
|
+
**Vague acceptance criteria**
|
|
37
|
+
- Replace subjective language ("looks good", "feels responsive") with measurable conditions ("renders within 200ms", "meets WCAG 2.2 AA contrast ratio")
|
|
38
|
+
- Replace modal verbs ("should", "might", "could") with definitive language ("must", "will")
|
|
39
|
+
|
|
40
|
+
**Broad implementation scope**
|
|
41
|
+
- Replace broad paths (`src/`, `the frontend`) with specific file names or subdirectory names
|
|
42
|
+
- Remove glob patterns (`*`, `**`) from scope tables — use actual directory or file names
|
|
43
|
+
|
|
44
|
+
**File partition conflicts**
|
|
45
|
+
- If two parallel workstreams claim the same file, move one to a later phase with explicit dependency
|
|
46
|
+
- Or split the file's responsibilities across the two workstreams so each touches distinct files
|
|
47
|
+
|
|
48
|
+
**Missing dependency declarations**
|
|
49
|
+
- Add `depends on: Phase N` to phases that require output from earlier phases
|
|
50
|
+
|
|
51
|
+
**Placeholder text**
|
|
52
|
+
- Replace template filler ("2–3 sentences about…", "Description here") with real content derived from the feature description
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Output
|
|
57
|
+
|
|
58
|
+
Return the **complete corrected PRD** as raw Markdown starting with the `#` heading. Do not wrap the output in a code fence. Do not add explanatory prose before or after the PRD.
|