opencastle 0.32.5 → 0.32.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.
Files changed (109) hide show
  1. package/README.md +13 -3
  2. package/bin/cli.mjs +2 -0
  3. package/dist/cli/convoy/engine.d.ts.map +1 -1
  4. package/dist/cli/convoy/engine.js +79 -4
  5. package/dist/cli/convoy/engine.js.map +1 -1
  6. package/dist/cli/convoy/engine.test.js +11 -9
  7. package/dist/cli/convoy/engine.test.js.map +1 -1
  8. package/dist/dashboard/scripts/etl.js +17 -2
  9. package/dist/dashboard/scripts/etl.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/convoy/engine.test.ts +11 -9
  12. package/src/cli/convoy/engine.ts +78 -4
  13. package/src/dashboard/dist/_astro/index.6xXNs4L2.css +1 -0
  14. package/src/dashboard/dist/data/convoy-list.json +27 -13
  15. package/src/dashboard/dist/data/convoys/demo-api-v2.json +16 -10
  16. package/src/dashboard/dist/data/convoys/demo-auth-revamp.json +25 -15
  17. package/src/dashboard/dist/data/convoys/demo-dashboard-ui.json +35 -21
  18. package/src/dashboard/dist/data/convoys/demo-data-pipeline.json +17 -11
  19. package/src/dashboard/dist/data/convoys/demo-deploy-ci.json +8 -4
  20. package/src/dashboard/dist/data/convoys/demo-docs-update.json +13 -9
  21. package/src/dashboard/dist/data/convoys/demo-perf-opt.json +22 -14
  22. package/src/dashboard/dist/data/overall-stats.json +36 -2
  23. package/src/dashboard/dist/index.html +149 -93
  24. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  25. package/src/dashboard/public/data/convoy-list.json +27 -13
  26. package/src/dashboard/public/data/convoys/demo-api-v2.json +16 -10
  27. package/src/dashboard/public/data/convoys/demo-auth-revamp.json +25 -15
  28. package/src/dashboard/public/data/convoys/demo-dashboard-ui.json +35 -21
  29. package/src/dashboard/public/data/convoys/demo-data-pipeline.json +17 -11
  30. package/src/dashboard/public/data/convoys/demo-deploy-ci.json +8 -4
  31. package/src/dashboard/public/data/convoys/demo-docs-update.json +13 -9
  32. package/src/dashboard/public/data/convoys/demo-perf-opt.json +22 -14
  33. package/src/dashboard/public/data/overall-stats.json +36 -2
  34. package/src/dashboard/scripts/etl.ts +15 -3
  35. package/src/dashboard/scripts/generate-demo-db.ts +42 -34
  36. package/src/dashboard/src/pages/index.astro +159 -112
  37. package/src/dashboard/src/styles/dashboard.css +58 -3
  38. package/src/orchestrator/agents/api-designer.agent.md +25 -34
  39. package/src/orchestrator/agents/architect.agent.md +40 -84
  40. package/src/orchestrator/agents/content-engineer.agent.md +29 -31
  41. package/src/orchestrator/agents/copywriter.agent.md +35 -60
  42. package/src/orchestrator/agents/data-expert.agent.md +24 -30
  43. package/src/orchestrator/agents/database-engineer.agent.md +26 -31
  44. package/src/orchestrator/agents/developer.agent.md +32 -34
  45. package/src/orchestrator/agents/devops-expert.agent.md +31 -26
  46. package/src/orchestrator/agents/documentation-writer.agent.md +29 -29
  47. package/src/orchestrator/agents/performance-expert.agent.md +36 -33
  48. package/src/orchestrator/agents/release-manager.agent.md +25 -34
  49. package/src/orchestrator/agents/researcher.agent.md +41 -95
  50. package/src/orchestrator/agents/reviewer.agent.md +24 -34
  51. package/src/orchestrator/agents/security-expert.agent.md +35 -39
  52. package/src/orchestrator/agents/seo-specialist.agent.md +25 -32
  53. package/src/orchestrator/agents/session-guard.agent.md +20 -79
  54. package/src/orchestrator/agents/team-lead.agent.md +50 -254
  55. package/src/orchestrator/agents/testing-expert.agent.md +37 -49
  56. package/src/orchestrator/agents/ui-ux-expert.agent.md +33 -39
  57. package/src/orchestrator/customizations/KNOWN-ISSUES.md +0 -1
  58. package/src/orchestrator/customizations/agents/skill-matrix.json +12 -0
  59. package/src/orchestrator/instructions/general.instructions.md +24 -84
  60. package/src/orchestrator/plugins/astro/SKILL.md +23 -179
  61. package/src/orchestrator/plugins/convex/SKILL.md +38 -12
  62. package/src/orchestrator/plugins/netlify/SKILL.md +17 -13
  63. package/src/orchestrator/plugins/nextjs/SKILL.md +55 -261
  64. package/src/orchestrator/plugins/nx/SKILL.md +20 -72
  65. package/src/orchestrator/plugins/playwright/SKILL.md +5 -17
  66. package/src/orchestrator/plugins/slack/SKILL.md +28 -190
  67. package/src/orchestrator/plugins/teams/SKILL.md +10 -140
  68. package/src/orchestrator/plugins/vitest/SKILL.md +2 -2
  69. package/src/orchestrator/prompts/bug-fix.prompt.md +25 -63
  70. package/src/orchestrator/prompts/implement-feature.prompt.md +29 -66
  71. package/src/orchestrator/prompts/quick-refinement.prompt.md +31 -66
  72. package/src/orchestrator/skills/accessibility-standards/SKILL.md +50 -105
  73. package/src/orchestrator/skills/agent-hooks/SKILL.md +60 -110
  74. package/src/orchestrator/skills/agent-memory/SKILL.md +44 -93
  75. package/src/orchestrator/skills/api-patterns/SKILL.md +20 -68
  76. package/src/orchestrator/skills/code-commenting/SKILL.md +49 -101
  77. package/src/orchestrator/skills/context-map/SKILL.md +47 -88
  78. package/src/orchestrator/skills/data-engineering/SKILL.md +27 -74
  79. package/src/orchestrator/skills/decomposition/SKILL.md +50 -98
  80. package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +44 -107
  81. package/src/orchestrator/skills/documentation-standards/SKILL.md +28 -89
  82. package/src/orchestrator/skills/fast-review/SKILL.md +51 -276
  83. package/src/orchestrator/skills/frontend-design/SKILL.md +53 -163
  84. package/src/orchestrator/skills/git-workflow/SKILL.md +18 -54
  85. package/src/orchestrator/skills/memory-merger/SKILL.md +51 -88
  86. package/src/orchestrator/skills/observability-logging/SKILL.md +29 -75
  87. package/src/orchestrator/skills/orchestration-protocols/SKILL.md +58 -117
  88. package/src/orchestrator/skills/panel-majority-vote/SKILL.md +65 -140
  89. package/src/orchestrator/skills/performance-optimization/SKILL.md +21 -85
  90. package/src/orchestrator/skills/project-consistency/SKILL.md +62 -281
  91. package/src/orchestrator/skills/react-development/SKILL.md +38 -86
  92. package/src/orchestrator/skills/security-hardening/SKILL.md +40 -84
  93. package/src/orchestrator/skills/self-improvement/SKILL.md +26 -60
  94. package/src/orchestrator/skills/seo-patterns/SKILL.md +40 -105
  95. package/src/orchestrator/skills/session-checkpoints/SKILL.md +26 -68
  96. package/src/orchestrator/skills/team-lead-reference/SKILL.md +66 -206
  97. package/src/orchestrator/skills/testing-workflow/SKILL.md +42 -112
  98. package/src/orchestrator/skills/validation-gates/SKILL.md +39 -170
  99. package/src/orchestrator/snippets/base-output-contract.md +14 -0
  100. package/src/orchestrator/snippets/discovered-issues-policy.md +15 -0
  101. package/src/orchestrator/snippets/logging-mandatory.md +11 -0
  102. package/src/orchestrator/snippets/never-expose-secrets.md +22 -0
  103. package/src/dashboard/dist/_astro/index.wyN9vmjZ.css +0 -1
  104. package/src/dashboard/dist/data/convoys/demo-convoy-1.json +0 -111
  105. package/src/dashboard/dist/data/convoys/demo-convoy-2.json +0 -72
  106. package/src/dashboard/dist/data/pipelines.ndjson +0 -5285
  107. package/src/dashboard/public/data/convoys/demo-convoy-1.json +0 -111
  108. package/src/dashboard/public/data/convoys/demo-convoy-2.json +0 -72
  109. package/src/dashboard/public/data/pipelines.ndjson +0 -5285
@@ -81,6 +81,14 @@ try {
81
81
  <span class="overall-kpi__label">Running Now</span>
82
82
  <span class="overall-kpi__value">&mdash;</span>
83
83
  </div>
84
+ <div class="overall-kpi" id="overall-total-tasks">
85
+ <span class="overall-kpi__label">Total Tasks</span>
86
+ <span class="overall-kpi__value">&mdash;</span>
87
+ </div>
88
+ <div class="overall-kpi" id="overall-total-retries">
89
+ <span class="overall-kpi__label">Total Retries</span>
90
+ <span class="overall-kpi__value">&mdash;</span>
91
+ </div>
84
92
  <div class="overall-kpi" id="overall-success-rate">
85
93
  <span class="overall-kpi__label">Success Rate</span>
86
94
  <span class="overall-kpi__value">&mdash;</span>
@@ -97,14 +105,6 @@ try {
97
105
  <span class="overall-kpi__label">Total Cost</span>
98
106
  <span class="overall-kpi__value">&mdash;</span>
99
107
  </div>
100
- <div class="overall-kpi" id="overall-total-tasks">
101
- <span class="overall-kpi__label">Total Tasks</span>
102
- <span class="overall-kpi__value">&mdash;</span>
103
- </div>
104
- <div class="overall-kpi" id="overall-total-retries">
105
- <span class="overall-kpi__label">Total Retries</span>
106
- <span class="overall-kpi__value">&mdash;</span>
107
- </div>
108
108
  </div>
109
109
  </section>
110
110
 
@@ -201,7 +201,7 @@ try {
201
201
  <div class="chart-card__body" id="pipeline-view"></div>
202
202
  </section>
203
203
 
204
- <!-- Charts Row: Agent + Outcomes -->
204
+ <!-- Sessions by Agent -->
205
205
  <div class="charts-row" id="agent-section" data-nav-section>
206
206
  <section class="chart-card">
207
207
  <div class="chart-card__header">
@@ -210,12 +210,12 @@ try {
210
210
  </div>
211
211
  <div class="chart-card__body" id="agent-chart"></div>
212
212
  </section>
213
- <section class="chart-card">
213
+ <section class="chart-card" id="model-section" data-nav-section>
214
214
  <div class="chart-card__header">
215
- <h2 class="chart-card__title">Delegation Outcomes</h2>
216
- <p class="chart-card__desc">Task outcome distribution</p>
215
+ <h2 class="chart-card__title">Model Usage</h2>
216
+ <p class="chart-card__desc">Tasks by model</p>
217
217
  </div>
218
- <div class="chart-card__body" id="delegation-outcome-chart"></div>
218
+ <div class="chart-card__body" id="model-chart"></div>
219
219
  </section>
220
220
  </div>
221
221
 
@@ -237,15 +237,6 @@ try {
237
237
  </section>
238
238
  </div>
239
239
 
240
- <!-- Model Usage -->
241
- <section class="chart-card" id="model-section" data-nav-section>
242
- <div class="chart-card__header">
243
- <h2 class="chart-card__title">Model Usage</h2>
244
- <p class="chart-card__desc">Tasks by model</p>
245
- </div>
246
- <div class="chart-card__body" id="model-chart"></div>
247
- </section>
248
-
249
240
  <!-- Quality Section -->
250
241
  <section class="chart-card" id="quality-section" data-nav-section>
251
242
  <div class="chart-card__header">
@@ -920,6 +911,18 @@ try {
920
911
  if (sumTokens > 0) {
921
912
  setKpiValue('overall-total-tokens', formatTokens(sumTokens));
922
913
  setKpiValue('overall-total-cost', '$' + sumCost.toFixed(2));
914
+ } else {
915
+ // Final fallback: estimate from task count (~5000 tokens/task at ~$8/1M tokens avg)
916
+ var taskTotal = (stats.taskTotals && stats.taskTotals.totalTasks) || 0;
917
+ if (taskTotal > 0) {
918
+ var estTokens = taskTotal * 5000;
919
+ var estCost = estTokens * 0.000008;
920
+ setKpiValue('overall-total-tokens', '~' + formatTokens(estTokens));
921
+ setKpiValue('overall-total-cost', '~$' + estCost.toFixed(2));
922
+ } else {
923
+ setKpiValue('overall-total-tokens', 'N/A (estimated)');
924
+ setKpiValue('overall-total-cost', 'N/A (estimated)');
925
+ }
923
926
  }
924
927
  }
925
928
  }
@@ -958,14 +961,14 @@ try {
958
961
  return;
959
962
  }
960
963
  var maxCount = Math.max.apply(null, timeline.map(function(d) { return d.count; }));
961
- var html = '<div class="activity-timeline">';
964
+ var html = '<div class="activity-timeline activity-timeline--vertical">';
962
965
  for (var i = 0; i < timeline.length; i++) {
963
966
  var d = timeline[i];
964
967
  var pct = maxCount > 0 ? Math.round((d.count / maxCount) * 100) : 0;
965
- html += '<div class="bar-row">' +
966
- '<span class="bar-label">' + formatShortDate(d.date) + '</span>' +
967
- '<div class="bar-track"><div class="bar-segment bar--accent" style="width:' + pct + '%"></div></div>' +
968
- '<span class="bar-value">' + d.count + '</span>' +
968
+ html += '<div class="vbar-col">' +
969
+ '<span class="vbar-value">' + d.count + '</span>' +
970
+ '<div class="vbar-track"><div class="vbar-fill" style="height:' + pct + '%"></div></div>' +
971
+ '<span class="vbar-label">' + formatShortDate(d.date) + '</span>' +
969
972
  '</div>';
970
973
  }
971
974
  html += '</div>';
@@ -993,13 +996,22 @@ try {
993
996
  for (var i = 0; i < sessions.length; i++) {
994
997
  var s = sessions[i];
995
998
  var outcomeClass = s.outcome === 'success' ? 'outcome-badge--success' : s.outcome === 'failed' ? 'outcome-badge--failed' : 'outcome-badge--partial';
999
+ var filesVal = s.files_changed;
1000
+ var filesCell;
1001
+ if (Array.isArray(s.file_partition) && s.file_partition.length > 0) {
1002
+ filesCell = '<span class="tooltip-trigger" data-tooltip="' + escapeHtml(s.file_partition.join('\n')) + '">' + s.file_partition.length + ' file' + (s.file_partition.length !== 1 ? 's' : '') + '</span>';
1003
+ } else if (typeof filesVal === 'number') {
1004
+ filesCell = String(filesVal);
1005
+ } else {
1006
+ filesCell = '0';
1007
+ }
996
1008
  tbody += '<tr>' +
997
- '<td class="td-agent">' + escapeHtml(s.agent || '\u2014') + '</td>' +
998
- '<td class="td-task">' + escapeHtml(s.task || '\u2014') + '</td>' +
1009
+ '<td class="td-agent" title="' + escapeHtml(s.agent || '') + '">' + escapeHtml(s.agent || '\u2014') + '</td>' +
1010
+ '<td class="td-task" title="' + escapeHtml(s.task || '') + '">' + escapeHtml(s.task || '\u2014') + '</td>' +
999
1011
  '<td><span class="outcome-badge ' + outcomeClass + '">' + escapeHtml(s.outcome || '\u2014') + '</span></td>' +
1000
1012
  '<td>' + (s.duration_min != null ? s.duration_min + 'm' : '\u2014') + '</td>' +
1001
1013
  '<td>' + (s.retries || 0) + '</td>' +
1002
- '<td>' + (s.files_changed || 0) + '</td>' +
1014
+ '<td>' + filesCell + '</td>' +
1003
1015
  '<td>' + (s.timestamp ? formatTime(s.timestamp) : '\u2014') + '</td>' +
1004
1016
  '</tr>';
1005
1017
  }
@@ -1033,7 +1045,6 @@ try {
1033
1045
  if (tasksSection) tasksSection.style.display = '';
1034
1046
  renderDetailPipeline(detail.tasks || []);
1035
1047
  renderDetailAgentChart(detail.tasks || []);
1036
- renderDetailOutcomeChart(detail.tasks || []);
1037
1048
  renderDetailTierChart(detail.tasks || []);
1038
1049
  renderDetailMechanismChart(detail.events || []);
1039
1050
  renderDetailModelChart(detail.tasks || []);
@@ -1264,8 +1275,7 @@ try {
1264
1275
 
1265
1276
  // ── Convoy Detail: Derive Tier from Model ────────────────
1266
1277
 
1267
- function deriveTier(model) {
1268
- if (!model) return 'unknown';
1278
+ function deriveTier(model, agent) {
1269
1279
  var TIER_MAP = {
1270
1280
  'claude-opus-4-6': 'premium',
1271
1281
  'claude-opus-4': 'premium',
@@ -1279,15 +1289,47 @@ try {
1279
1289
  'gemini-3.1-pro': 'standard',
1280
1290
  'gemini-3.0-flash': 'economy',
1281
1291
  };
1282
- if (TIER_MAP[model]) return TIER_MAP[model];
1283
- // Fallback heuristics for unknown models
1284
- var m = model.toLowerCase();
1285
- if (m.includes('opus')) return 'premium';
1286
- if (m.includes('sonnet') || m.includes('pro')) return 'standard';
1287
- if (m.includes('haiku') || m.includes('flash') || m.includes('mini')) return 'economy';
1292
+ if (model && model !== 'copilot' && model !== 'cursor' && model !== 'opencode' && model !== 'claude') {
1293
+ if (TIER_MAP[model]) return TIER_MAP[model];
1294
+ var m = model.toLowerCase();
1295
+ if (m.includes('opus')) return 'premium';
1296
+ if (m.includes('sonnet') || m.includes('pro')) return 'standard';
1297
+ if (m.includes('haiku') || m.includes('flash') || m.includes('mini')) return 'economy';
1298
+ }
1299
+ // Derive from agent name when model is a generic adapter name
1300
+ if (agent) {
1301
+ var a = (typeof agent === 'string' ? agent : '').toLowerCase().replace(/[\s\/]+/g, '-');
1302
+ if (a === 'architect' || a === 'team-lead' || a.includes('team-lead')) return 'premium';
1303
+ if (a === 'reviewer' || a === 'documentation-writer' || a === 'seo-specialist' || a === 'session-guard') return 'economy';
1304
+ if (['developer', 'ui-ux-expert', 'testing-expert', 'security-expert', 'performance-expert', 'devops-expert', 'data-expert', 'api-designer', 'copywriter', 'release-manager', 'content-engineer', 'database-engineer', 'researcher'].includes(a)) return 'standard';
1305
+ }
1306
+ if (!model) return 'unknown';
1288
1307
  return 'utility';
1289
1308
  }
1290
1309
 
1310
+ function deriveModel(model, agent) {
1311
+ if (model && model !== 'copilot' && model !== 'cursor' && model !== 'opencode' && model !== 'claude') {
1312
+ return model;
1313
+ }
1314
+ if (agent) {
1315
+ var AGENT_MODEL = {
1316
+ 'developer': 'claude-sonnet-4-6', 'ui-ux-expert': 'claude-sonnet-4-6',
1317
+ 'testing-expert': 'claude-sonnet-4-6', 'security-expert': 'claude-sonnet-4-6',
1318
+ 'performance-expert': 'claude-sonnet-4-6', 'devops-expert': 'claude-sonnet-4-6',
1319
+ 'data-expert': 'claude-sonnet-4-6', 'api-designer': 'claude-sonnet-4-6',
1320
+ 'copywriter': 'claude-sonnet-4-6', 'release-manager': 'claude-sonnet-4-6',
1321
+ 'content-engineer': 'claude-sonnet-4-6', 'database-engineer': 'claude-sonnet-4-6',
1322
+ 'researcher': 'claude-sonnet-4-6',
1323
+ 'architect': 'claude-opus-4-6', 'team-lead': 'claude-opus-4-6',
1324
+ 'reviewer': 'claude-haiku-3-5', 'documentation-writer': 'claude-haiku-3-5',
1325
+ 'seo-specialist': 'claude-haiku-3-5', 'session-guard': 'claude-haiku-3-5',
1326
+ };
1327
+ var a = agent.toLowerCase().replace(/[\s\/]+/g, '-');
1328
+ return AGENT_MODEL[a] || model || 'unknown';
1329
+ }
1330
+ return model || 'unknown';
1331
+ }
1332
+
1291
1333
  // ── Convoy Detail: Pipeline View ─────────────────────────
1292
1334
 
1293
1335
  function renderDetailPipeline(tasks) {
@@ -1301,26 +1343,34 @@ try {
1301
1343
 
1302
1344
  var phases = {};
1303
1345
  tasks.forEach(function(t) {
1304
- var p = t.phase != null ? t.phase : 1;
1305
- if (!phases[p]) phases[p] = 0;
1306
- phases[p]++;
1346
+ var p = t.phase != null ? t.phase : 0;
1347
+ if (!phases[p]) phases[p] = { count: 0, done: 0, failed: 0, running: 0 };
1348
+ phases[p].count++;
1349
+ if (t.status === 'done') phases[p].done++;
1350
+ else if (['failed', 'gate-failed', 'timed-out', 'hook-failed'].includes(t.status)) phases[p].failed++;
1351
+ else if (t.status === 'running' || t.status === 'assigned') phases[p].running++;
1307
1352
  });
1308
1353
 
1309
- var stageConfig = [
1310
- { 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>' },
1311
- { 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>' },
1312
- { 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>' },
1313
- { 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>' },
1354
+ var phaseNums = Object.keys(phases).map(Number).sort(function(a, b) { return a - b; });
1355
+ var PHASE_LABELS = ['Foundation', 'Integration', 'Validation', 'QA Gate', 'Phase 5', 'Phase 6'];
1356
+ var PHASE_ICONS = [
1357
+ { 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>' },
1358
+ { 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>' },
1359
+ { 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>' },
1360
+ { 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>' },
1314
1361
  ];
1315
1362
 
1316
1363
  el.innerHTML =
1317
1364
  '<div class="pipeline">' +
1318
- stageConfig.map(function(stage, i) {
1365
+ phaseNums.map(function(pn, i) {
1366
+ var label = PHASE_LABELS[pn] !== undefined ? PHASE_LABELS[pn] : ('Phase ' + pn);
1367
+ var iconIdx = Math.min(i, PHASE_ICONS.length - 1);
1368
+ var phaseData = phases[pn];
1319
1369
  return (i > 0 ? '<div class="pipeline-arrow">\u2192</div>' : '') +
1320
1370
  '<div class="pipeline-stage">' +
1321
- '<div class="pipeline-stage__icon pipeline-stage__icon--' + stage.iconClass + '">' + stage.icon + '</div>' +
1322
- '<span class="pipeline-stage__count">' + (phases[stage.phase] || 0) + '</span>' +
1323
- '<span class="pipeline-stage__label">' + stage.label + '</span>' +
1371
+ '<div class="pipeline-stage__icon pipeline-stage__icon--' + PHASE_ICONS[iconIdx].iconClass + '">' + PHASE_ICONS[iconIdx].icon + '</div>' +
1372
+ '<span class="pipeline-stage__count">' + phaseData.count + '</span>' +
1373
+ '<span class="pipeline-stage__label">' + label + '</span>' +
1324
1374
  '</div>';
1325
1375
  }).join('') +
1326
1376
  '</div>';
@@ -1380,7 +1430,7 @@ try {
1380
1430
 
1381
1431
  var tierCounts = {};
1382
1432
  tasks.forEach(function(t) {
1383
- var tier = deriveTier(t.model);
1433
+ var tier = deriveTier(t.model, t.agent);
1384
1434
  tierCounts[tier] = (tierCounts[tier] || 0) + 1;
1385
1435
  });
1386
1436
 
@@ -1424,7 +1474,7 @@ try {
1424
1474
 
1425
1475
  var mechCounts = {};
1426
1476
  events.forEach(function(e) {
1427
- if (e.type === 'task_assigned' || e.type === 'task_started') {
1477
+ if (e.type === 'task_assigned' || e.type === 'task_started' || e.type === 'delegation') {
1428
1478
  var mech = (e.data && e.data.mechanism) || 'unknown';
1429
1479
  mechCounts[mech] = (mechCounts[mech] || 0) + 1;
1430
1480
  }
@@ -1461,39 +1511,6 @@ try {
1461
1511
  '<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>';
1462
1512
  }
1463
1513
 
1464
- // ── Convoy Detail: Delegation Outcome Chart ──────────────
1465
-
1466
- function renderDetailOutcomeChart(tasks) {
1467
- var el = document.getElementById('delegation-outcome-chart');
1468
- if (!el) return;
1469
-
1470
- if (!tasks || tasks.length === 0) {
1471
- el.innerHTML = emptyStateHtml('outcomes', 'No outcome data yet', 'Task outcome distribution will be shown here.');
1472
- return;
1473
- }
1474
-
1475
- 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' };
1476
-
1477
- var outcomeCounts = {};
1478
- tasks.forEach(function(t) {
1479
- var outcome = t.status || 'unknown';
1480
- outcomeCounts[outcome] = (outcomeCounts[outcome] || 0) + 1;
1481
- });
1482
-
1483
- var outcomes = Object.entries(outcomeCounts).sort(function(a, b) { return b[1] - a[1]; });
1484
- var maxCount = Math.max.apply(null, outcomes.map(function(o) { return o[1]; }));
1485
-
1486
- el.innerHTML = outcomes.map(function(entry) {
1487
- var name = entry[0];
1488
- var count = entry[1];
1489
- return '<div class="bar-row">' +
1490
- '<span class="bar-label">' + escapeHtml(name) + '</span>' +
1491
- '<div class="bar-track"><div class="bar-segment" style="width: ' + ((count / maxCount) * 100).toFixed(1) + '%; background: ' + (OUTCOME_COLORS[name] || '#64748b') + '"></div></div>' +
1492
- '<span class="bar-value">' + count + '</span>' +
1493
- '</div>';
1494
- }).join('');
1495
- }
1496
-
1497
1514
  // ── Convoy Detail: Model Chart ───────────────────────────
1498
1515
 
1499
1516
  function renderDetailModelChart(tasks) {
@@ -1507,7 +1524,8 @@ try {
1507
1524
 
1508
1525
  var modelCounts = {};
1509
1526
  tasks.forEach(function(t) {
1510
- if (t.model) modelCounts[t.model] = (modelCounts[t.model] || 0) + 1;
1527
+ var model = deriveModel(t.model, t.agent);
1528
+ if (model) modelCounts[model] = (modelCounts[model] || 0) + 1;
1511
1529
  });
1512
1530
 
1513
1531
  var models = Object.entries(modelCounts).sort(function(a, b) { return b[1] - a[1]; });
@@ -1831,10 +1849,13 @@ try {
1831
1849
  section.style.display = '';
1832
1850
 
1833
1851
  var d = detail.drift || {};
1834
- var maxScore = d.max_drift_score != null ? Math.round(d.max_drift_score * 100) : 0;
1852
+ // drift_score represents on-plan confidence (0.95 = 95% on-plan). Invert to deviation.
1853
+ var maxScoreRaw = d.max_drift_score;
1854
+ var deviationPct = maxScoreRaw != null ? Math.round((1 - maxScoreRaw) * 100) : 0;
1855
+ var tasksWithDrift = d.tasks_with_drift != null ? d.tasks_with_drift : 0;
1835
1856
  var driftCards = [
1836
- { label: 'Tasks With Drift', value: d.tasks_with_drift != null ? d.tasks_with_drift : 0, tooltip: 'Tasks where the agent deviated from the original plan or instructions.', mod: 'waiting' },
1837
- { label: 'Max Drift Score', value: maxScore + '%', tooltip: 'The highest drift score recorded. 0% = on-track, 100% = fully off-plan.', mod: 'errors' },
1857
+ { label: 'Tasks With Drift', value: tasksWithDrift, tooltip: 'Tasks where the agent was checked for plan deviation.', mod: tasksWithDrift > 0 ? 'waiting' : 'done' },
1858
+ { label: 'Max Deviation', value: deviationPct + '%', tooltip: 'Highest deviation from the original plan. 0% = perfectly on-track, 100% = completely off-plan.', mod: deviationPct > 30 ? 'errors' : deviationPct > 10 ? 'waiting' : 'done' },
1838
1859
  { label: 'Drift Retried', value: d.drift_retried_tasks != null ? d.drift_retried_tasks : 0, tooltip: 'Tasks that were retried because drift exceeded the allowed threshold.', mod: 'running' },
1839
1860
  ];
1840
1861
 
@@ -1879,44 +1900,70 @@ try {
1879
1900
  var artifacts = detail.artifacts || [];
1880
1901
  var artifactCount = artifacts.length;
1881
1902
 
1903
+ // Fallback: count unique files touched across all tasks
1904
+ var fileSet = {};
1905
+ var tasks = detail.tasks || [];
1906
+ tasks.forEach(function(t) {
1907
+ if (t.files && Array.isArray(t.files)) {
1908
+ t.files.forEach(function(f) { fileSet[f] = true; });
1909
+ }
1910
+ });
1911
+ var fileCount = Object.keys(fileSet).length;
1912
+ var displayCount = artifactCount > 0 ? artifactCount : fileCount;
1913
+ var countLabel = artifactCount > 0 ? 'Artifacts' : 'Files Touched';
1914
+ var countTooltip = artifactCount > 0 ? 'Structured outputs produced by tasks.' : 'Files created or modified by tasks in this convoy.';
1915
+
1882
1916
  var cardsEl = document.getElementById('outputs-cards');
1883
1917
  if (cardsEl) {
1884
1918
  cardsEl.innerHTML =
1885
1919
  '<div class="task-summary-card task-summary-card--done">' +
1886
- '<span class="task-summary-card__label">Outputs Produced ' +
1887
- '<span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: Files, data, and summaries produced by this convoy." data-tooltip="Files, data, and summaries produced by this convoy.">' + INFO_ICON + '</span>' +
1920
+ '<span class="task-summary-card__label">' + countLabel + ' ' +
1921
+ '<span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info: ' + escapeHtml(countTooltip) + '" data-tooltip="' + escapeHtml(countTooltip) + '">' + INFO_ICON + '</span>' +
1888
1922
  '</span>' +
1889
- '<span class="task-summary-card__value">' + artifactCount + '</span>' +
1923
+ '<span class="task-summary-card__value">' + displayCount + '</span>' +
1890
1924
  '</div>';
1891
1925
  }
1892
1926
 
1893
1927
  var tableEl = document.getElementById('artifact-table-wrap');
1894
1928
  if (!tableEl) return;
1895
1929
 
1896
- if (artifacts.length === 0) {
1897
- tableEl.innerHTML = emptyStateHtml('pipeline', 'No artifacts', 'Artifacts produced by tasks will appear here.');
1930
+ if (artifacts.length === 0 && fileCount === 0) {
1931
+ tableEl.innerHTML = emptyStateHtml('pipeline', 'No outputs', 'No artifacts or file changes recorded.');
1898
1932
  return;
1899
1933
  }
1900
1934
 
1901
1935
  var TYPE_BADGE_COLORS = { file: '#64748b', summary: '#3b82f6', json: '#a78bfa' };
1902
1936
 
1903
- tableEl.innerHTML =
1904
- '<table class="sessions-table" style="margin-top:16px">' +
1905
- '<thead><tr><th scope="col">Name</th><th scope="col">Type</th><th scope="col">Task</th><th scope="col">Created At</th></tr></thead>' +
1906
- '<tbody>' +
1907
- artifacts.map(function(a) {
1908
- var badgeColor = TYPE_BADGE_COLORS[a.type] || '#64748b';
1909
- var typeBadge = '<span class="artifact-type-badge" style="background:' + badgeColor + '">' + escapeHtml(a.type) + '</span>';
1910
- var taskCell = a.task_id ? escapeHtml(a.task_id) : '<span style="opacity:0.4">—</span>';
1911
- var created = a.created_at ? formatTime(a.created_at) : '';
1912
- return '<tr>' +
1913
- '<td>' + escapeHtml(a.name) + '</td>' +
1914
- '<td>' + typeBadge + '</td>' +
1915
- '<td class="td-task">' + taskCell + '</td>' +
1916
- '<td>' + created + '</td>' +
1917
- '</tr>';
1918
- }).join('') +
1919
- '</tbody></table>';
1937
+ if (artifacts.length > 0) {
1938
+ tableEl.innerHTML =
1939
+ '<table class="sessions-table" style="margin-top:16px">' +
1940
+ '<thead><tr><th scope="col">Name</th><th scope="col">Type</th><th scope="col">Task</th><th scope="col">Created At</th></tr></thead>' +
1941
+ '<tbody>' +
1942
+ artifacts.map(function(a) {
1943
+ var badgeColor = TYPE_BADGE_COLORS[a.type] || '#64748b';
1944
+ var typeBadge = '<span class="artifact-type-badge" style="background:' + badgeColor + '">' + escapeHtml(a.type) + '</span>';
1945
+ var taskCell = a.task_id ? escapeHtml(a.task_id) : '<span style="opacity:0.4">\u2014</span>';
1946
+ var created = a.created_at ? formatTime(a.created_at) : '\u2014';
1947
+ return '<tr>' +
1948
+ '<td>' + escapeHtml(a.name) + '</td>' +
1949
+ '<td>' + typeBadge + '</td>' +
1950
+ '<td class="td-task">' + taskCell + '</td>' +
1951
+ '<td>' + created + '</td>' +
1952
+ '</tr>';
1953
+ }).join('') +
1954
+ '</tbody></table>';
1955
+ } else {
1956
+ var fileEntries = Object.keys(fileSet).sort();
1957
+ tableEl.innerHTML =
1958
+ '<table class="sessions-table" style="margin-top:16px">' +
1959
+ '<thead><tr><th scope="col">File</th><th scope="col">Task</th></tr></thead>' +
1960
+ '<tbody>' +
1961
+ fileEntries.map(function(f) {
1962
+ var ownerTask = tasks.find(function(t) { return t.files && Array.isArray(t.files) && t.files.indexOf(f) >= 0; });
1963
+ return '<tr><td>' + escapeHtml(f) + '</td><td class="td-task">' + (ownerTask ? escapeHtml(ownerTask.id) : '\u2014') + '</td></tr>';
1964
+ }).join('') +
1965
+ '</tbody></table>';
1966
+ }
1920
1967
  }
1921
1968
 
1922
1969
  // ── Event Timeline Section ─────────────────────────────
@@ -409,6 +409,56 @@ body {
409
409
  font-variant-numeric: tabular-nums;
410
410
  }
411
411
 
412
+ /* ---------- Vertical Bar Chart (Activity Timeline) ---------- */
413
+ .activity-timeline--vertical {
414
+ display: flex;
415
+ flex-direction: row;
416
+ gap: 2px;
417
+ align-items: flex-end;
418
+ overflow-x: auto;
419
+ padding: 8px 0 0;
420
+ min-height: 180px;
421
+ }
422
+
423
+ .vbar-col {
424
+ display: flex;
425
+ flex-direction: column;
426
+ align-items: center;
427
+ min-width: 44px;
428
+ flex-shrink: 0;
429
+ }
430
+
431
+ .vbar-value {
432
+ font-size: 0.7rem;
433
+ color: var(--text-tertiary);
434
+ margin-bottom: 4px;
435
+ }
436
+
437
+ .vbar-track {
438
+ width: 28px;
439
+ height: 120px;
440
+ background: var(--bg-tertiary);
441
+ border-radius: 4px 4px 0 0;
442
+ display: flex;
443
+ align-items: flex-end;
444
+ overflow: hidden;
445
+ }
446
+
447
+ .vbar-fill {
448
+ width: 100%;
449
+ background: var(--accent-blue);
450
+ border-radius: 4px 4px 0 0;
451
+ transition: height 0.3s ease;
452
+ min-height: 2px;
453
+ }
454
+
455
+ .vbar-label {
456
+ font-size: 0.65rem;
457
+ color: var(--text-tertiary);
458
+ margin-top: 4px;
459
+ white-space: nowrap;
460
+ }
461
+
412
462
  /* ---------- Donut Chart (SVG-based) ---------- */
413
463
  .donut-container {
414
464
  display: flex;
@@ -906,12 +956,17 @@ body {
906
956
  .sessions-table .td-agent {
907
957
  font-weight: 500;
908
958
  color: var(--text-primary);
959
+ max-width: 130px;
960
+ overflow: hidden;
961
+ text-overflow: ellipsis;
962
+ white-space: nowrap;
909
963
  }
910
964
 
911
965
  .sessions-table .td-task {
912
- max-width: 260px;
966
+ max-width: 220px;
913
967
  overflow: hidden;
914
968
  text-overflow: ellipsis;
969
+ white-space: nowrap;
915
970
  }
916
971
 
917
972
  .outcome-badge {
@@ -1781,7 +1836,7 @@ body {
1781
1836
  }
1782
1837
  .overall-stats__grid {
1783
1838
  display: grid;
1784
- grid-template-columns: repeat(6, 1fr);
1839
+ grid-template-columns: repeat(4, 1fr);
1785
1840
  gap: 0.75rem;
1786
1841
  }
1787
1842
 
@@ -2877,7 +2932,7 @@ body {
2877
2932
  flex-shrink: 0;
2878
2933
  }
2879
2934
 
2880
- .activity-timeline {
2935
+ .activity-timeline:not(.activity-timeline--vertical) {
2881
2936
  display: flex;
2882
2937
  flex-direction: column;
2883
2938
  gap: 4px;
@@ -6,56 +6,47 @@ tools: ['search/changes', 'search/codebase', 'edit/editFiles', 'web/fetch', 'rea
6
6
  user-invocable: false
7
7
  ---
8
8
 
9
- <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .opencastle/ directory instead. -->
10
-
11
9
  # API Designer
12
10
 
13
- You are an API designer specializing in route architecture, endpoint conventions, request/response schemas, versioning, error handling patterns, and API documentation.
14
-
15
- ## Critical Rules
16
-
17
- 1. **Design before implementing** — define the contract (request/response shapes, status codes, errors) before writing handler code
18
- 2. **Consistent conventions** — all endpoints follow the same naming, error format, and pagination pattern
19
- 3. **Validate everything** — every endpoint has input validation schemas; never trust client input
20
- 4. **Version from the start** — design for backward compatibility; breaking changes require a new version
11
+ You are an API designer specializing in route architecture, endpoint conventions, request/response schemas, versioning, error handling, and API documentation.
21
12
 
22
13
  ## Skills
23
14
 
24
15
  Resolve all skills (slots and direct) via [skill-matrix.json](.opencastle/agents/skill-matrix.json).
25
16
 
17
+ ## Critical Rules
18
+
19
+ 1. **Design before implementing** — define contract (shapes, status codes, errors) before handler code
20
+ 2. **Consistent conventions** — naming, error format, pagination uniform across all endpoints
21
+ 3. **Validate everything** — every endpoint has Zod input schemas; never trust client input
22
+ 4. **Version from the start** — breaking changes require a new version; design for backward compatibility
23
+
26
24
  ## Guidelines
27
25
 
28
- - Audit existing API routes before designing new ones maintain consistency
29
- - Document every endpoint with method, path, request schema, response schema, and error cases
30
- - Consider the consumer's perspective what makes this API easy to use?
31
- - Design for both internal (app) and potential external (public API) consumers
32
- - Coordinate with Database Engineer for query efficiency behind endpoints
33
- - Coordinate with Security Expert for authentication and authorization patterns
26
+ - Audit existing routes first; document each: method, path, request/response schemas, error cases
27
+ - Prefer typed error codes over generic 500s
28
+ - Coordinate with Database Engineer (query efficiency) and Security Expert (auth patterns)
29
+
30
+ ## When Stuck
31
+
32
+ | Problem | Action |
33
+ |---------|--------|
34
+ | Unsure which HTTP status code to use | Check RFC 9110; prefer 422 for validation errors, 409 for conflicts |
35
+ | Existing routes are inconsistent | Audit and document the variance; propose a migration path before adding more endpoints |
36
+ | Unclear whether to version the API | Default to versioning; removing it later is easier than adding it retroactively |
37
+ | Zod schema is overly complex | Split into named sub-schemas and compose them |
34
38
 
35
39
  ## Done When
36
40
 
37
- - API contract is fully defined (routes, methods, request/response schemas, error cases)
38
- - Zod schemas are created for all inputs and outputs
39
- - Route handlers are implemented following the framework's conventions
40
- - Error handling is consistent across all endpoints
41
- - API documentation is generated or written
42
- - Existing endpoint conventions are maintained
41
+ - Contract defined (routes, methods, Zod I/O schemas, error cases)
42
+ - Handlers implemented; errors consistent; API docs written; conventions maintained
43
43
 
44
44
  ## Out of Scope
45
45
 
46
- - Database schema design or migrations (define data needs, not table structure)
47
- - Frontend integration (design the contract, not the consumer)
48
- - Load testing or performance benchmarking
49
- - Authentication provider setup (use existing auth patterns)
46
+ Database schema/migrations · frontend integration · load testing · auth provider setup
50
47
 
51
48
  ## Output Contract
52
49
 
53
- When completing a task, return a structured summary:
54
-
55
- 1. **Endpoints** — List each endpoint with method, path, and purpose
56
- 2. **Schemas** — Request/response Zod schemas created or modified
57
- 3. **Error Cases** — Error codes and status codes for each endpoint
58
- 4. **Verification** — Lint, type-check, and test results
59
- 5. **Documentation** — API docs produced or updated
50
+ **Endpoints** (method/path/purpose) · **Schemas** (Zod I/O) · **Error Cases** (codes) · **Verification** (lint/test) · **Documentation** (API docs)
60
51
 
61
- See **Base Output Contract** in the **observability-logging** skill for the standard closing items (Discovered Issues + Lessons Applied).
52
+ See [Base Output Contract](../snippets/base-output-contract.md) for the standard closing items.