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.
- package/README.md +13 -3
- package/bin/cli.mjs +2 -0
- package/dist/cli/convoy/engine.d.ts.map +1 -1
- package/dist/cli/convoy/engine.js +79 -4
- package/dist/cli/convoy/engine.js.map +1 -1
- package/dist/cli/convoy/engine.test.js +11 -9
- package/dist/cli/convoy/engine.test.js.map +1 -1
- package/dist/dashboard/scripts/etl.js +17 -2
- package/dist/dashboard/scripts/etl.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/convoy/engine.test.ts +11 -9
- package/src/cli/convoy/engine.ts +78 -4
- package/src/dashboard/dist/_astro/index.6xXNs4L2.css +1 -0
- package/src/dashboard/dist/data/convoy-list.json +27 -13
- package/src/dashboard/dist/data/convoys/demo-api-v2.json +16 -10
- package/src/dashboard/dist/data/convoys/demo-auth-revamp.json +25 -15
- package/src/dashboard/dist/data/convoys/demo-dashboard-ui.json +35 -21
- package/src/dashboard/dist/data/convoys/demo-data-pipeline.json +17 -11
- package/src/dashboard/dist/data/convoys/demo-deploy-ci.json +8 -4
- package/src/dashboard/dist/data/convoys/demo-docs-update.json +13 -9
- package/src/dashboard/dist/data/convoys/demo-perf-opt.json +22 -14
- package/src/dashboard/dist/data/overall-stats.json +36 -2
- package/src/dashboard/dist/index.html +149 -93
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/public/data/convoy-list.json +27 -13
- package/src/dashboard/public/data/convoys/demo-api-v2.json +16 -10
- package/src/dashboard/public/data/convoys/demo-auth-revamp.json +25 -15
- package/src/dashboard/public/data/convoys/demo-dashboard-ui.json +35 -21
- package/src/dashboard/public/data/convoys/demo-data-pipeline.json +17 -11
- package/src/dashboard/public/data/convoys/demo-deploy-ci.json +8 -4
- package/src/dashboard/public/data/convoys/demo-docs-update.json +13 -9
- package/src/dashboard/public/data/convoys/demo-perf-opt.json +22 -14
- package/src/dashboard/public/data/overall-stats.json +36 -2
- package/src/dashboard/scripts/etl.ts +15 -3
- package/src/dashboard/scripts/generate-demo-db.ts +42 -34
- package/src/dashboard/src/pages/index.astro +159 -112
- package/src/dashboard/src/styles/dashboard.css +58 -3
- package/src/orchestrator/agents/api-designer.agent.md +25 -34
- package/src/orchestrator/agents/architect.agent.md +40 -84
- package/src/orchestrator/agents/content-engineer.agent.md +29 -31
- package/src/orchestrator/agents/copywriter.agent.md +35 -60
- package/src/orchestrator/agents/data-expert.agent.md +24 -30
- package/src/orchestrator/agents/database-engineer.agent.md +26 -31
- package/src/orchestrator/agents/developer.agent.md +32 -34
- package/src/orchestrator/agents/devops-expert.agent.md +31 -26
- package/src/orchestrator/agents/documentation-writer.agent.md +29 -29
- package/src/orchestrator/agents/performance-expert.agent.md +36 -33
- package/src/orchestrator/agents/release-manager.agent.md +25 -34
- package/src/orchestrator/agents/researcher.agent.md +41 -95
- package/src/orchestrator/agents/reviewer.agent.md +24 -34
- package/src/orchestrator/agents/security-expert.agent.md +35 -39
- package/src/orchestrator/agents/seo-specialist.agent.md +25 -32
- package/src/orchestrator/agents/session-guard.agent.md +20 -79
- package/src/orchestrator/agents/team-lead.agent.md +50 -254
- package/src/orchestrator/agents/testing-expert.agent.md +37 -49
- package/src/orchestrator/agents/ui-ux-expert.agent.md +33 -39
- package/src/orchestrator/customizations/KNOWN-ISSUES.md +0 -1
- package/src/orchestrator/customizations/agents/skill-matrix.json +12 -0
- package/src/orchestrator/instructions/general.instructions.md +24 -84
- package/src/orchestrator/plugins/astro/SKILL.md +23 -179
- package/src/orchestrator/plugins/convex/SKILL.md +38 -12
- package/src/orchestrator/plugins/netlify/SKILL.md +17 -13
- package/src/orchestrator/plugins/nextjs/SKILL.md +55 -261
- package/src/orchestrator/plugins/nx/SKILL.md +20 -72
- package/src/orchestrator/plugins/playwright/SKILL.md +5 -17
- package/src/orchestrator/plugins/slack/SKILL.md +28 -190
- package/src/orchestrator/plugins/teams/SKILL.md +10 -140
- package/src/orchestrator/plugins/vitest/SKILL.md +2 -2
- package/src/orchestrator/prompts/bug-fix.prompt.md +25 -63
- package/src/orchestrator/prompts/implement-feature.prompt.md +29 -66
- package/src/orchestrator/prompts/quick-refinement.prompt.md +31 -66
- package/src/orchestrator/skills/accessibility-standards/SKILL.md +50 -105
- package/src/orchestrator/skills/agent-hooks/SKILL.md +60 -110
- package/src/orchestrator/skills/agent-memory/SKILL.md +44 -93
- package/src/orchestrator/skills/api-patterns/SKILL.md +20 -68
- package/src/orchestrator/skills/code-commenting/SKILL.md +49 -101
- package/src/orchestrator/skills/context-map/SKILL.md +47 -88
- package/src/orchestrator/skills/data-engineering/SKILL.md +27 -74
- package/src/orchestrator/skills/decomposition/SKILL.md +50 -98
- package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +44 -107
- package/src/orchestrator/skills/documentation-standards/SKILL.md +28 -89
- package/src/orchestrator/skills/fast-review/SKILL.md +51 -276
- package/src/orchestrator/skills/frontend-design/SKILL.md +53 -163
- package/src/orchestrator/skills/git-workflow/SKILL.md +18 -54
- package/src/orchestrator/skills/memory-merger/SKILL.md +51 -88
- package/src/orchestrator/skills/observability-logging/SKILL.md +29 -75
- package/src/orchestrator/skills/orchestration-protocols/SKILL.md +58 -117
- package/src/orchestrator/skills/panel-majority-vote/SKILL.md +65 -140
- package/src/orchestrator/skills/performance-optimization/SKILL.md +21 -85
- package/src/orchestrator/skills/project-consistency/SKILL.md +62 -281
- package/src/orchestrator/skills/react-development/SKILL.md +38 -86
- package/src/orchestrator/skills/security-hardening/SKILL.md +40 -84
- package/src/orchestrator/skills/self-improvement/SKILL.md +26 -60
- package/src/orchestrator/skills/seo-patterns/SKILL.md +40 -105
- package/src/orchestrator/skills/session-checkpoints/SKILL.md +26 -68
- package/src/orchestrator/skills/team-lead-reference/SKILL.md +66 -206
- package/src/orchestrator/skills/testing-workflow/SKILL.md +42 -112
- package/src/orchestrator/skills/validation-gates/SKILL.md +39 -170
- package/src/orchestrator/snippets/base-output-contract.md +14 -0
- package/src/orchestrator/snippets/discovered-issues-policy.md +15 -0
- package/src/orchestrator/snippets/logging-mandatory.md +11 -0
- package/src/orchestrator/snippets/never-expose-secrets.md +22 -0
- package/src/dashboard/dist/_astro/index.wyN9vmjZ.css +0 -1
- package/src/dashboard/dist/data/convoys/demo-convoy-1.json +0 -111
- package/src/dashboard/dist/data/convoys/demo-convoy-2.json +0 -72
- package/src/dashboard/dist/data/pipelines.ndjson +0 -5285
- package/src/dashboard/public/data/convoys/demo-convoy-1.json +0 -111
- package/src/dashboard/public/data/convoys/demo-convoy-2.json +0 -72
- 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">—</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">—</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">—</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">—</span>
|
|
@@ -97,14 +105,6 @@ try {
|
|
|
97
105
|
<span class="overall-kpi__label">Total Cost</span>
|
|
98
106
|
<span class="overall-kpi__value">—</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">—</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">—</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
|
-
<!--
|
|
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">
|
|
216
|
-
<p class="chart-card__desc">
|
|
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="
|
|
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="
|
|
966
|
-
'<span class="
|
|
967
|
-
'<div class="
|
|
968
|
-
'<span class="
|
|
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>' +
|
|
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 (
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
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 :
|
|
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
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
{
|
|
1313
|
-
{
|
|
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
|
-
|
|
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--' +
|
|
1322
|
-
'<span class="pipeline-stage__count">' +
|
|
1323
|
-
'<span class="pipeline-stage__label">' +
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1837
|
-
{ label: 'Max
|
|
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">
|
|
1887
|
-
'<span class="tooltip-trigger" tabindex="0" role="button" aria-label="Info:
|
|
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">' +
|
|
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
|
|
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
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
'<
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
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:
|
|
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(
|
|
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
|
|
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
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
-
|
|
38
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
52
|
+
See [Base Output Contract](../snippets/base-output-contract.md) for the standard closing items.
|