trackops 2.0.3 → 2.0.5

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 (103) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +695 -402
  3. package/bin/trackops.js +116 -116
  4. package/lib/config.js +326 -326
  5. package/lib/control.js +208 -208
  6. package/lib/env.js +244 -244
  7. package/lib/init.js +325 -325
  8. package/lib/locale.js +24 -0
  9. package/lib/opera-bootstrap.js +941 -874
  10. package/lib/opera.js +494 -477
  11. package/lib/preferences.js +74 -74
  12. package/lib/registry.js +214 -196
  13. package/lib/release.js +56 -56
  14. package/lib/runtime-state.js +144 -144
  15. package/lib/server.js +312 -207
  16. package/lib/skills.js +74 -57
  17. package/lib/workspace.js +260 -260
  18. package/locales/en.json +192 -166
  19. package/locales/es.json +192 -166
  20. package/package.json +61 -58
  21. package/scripts/postinstall-locale.js +21 -21
  22. package/scripts/skills-marketplace-smoke.js +124 -124
  23. package/scripts/smoke-tests.js +558 -554
  24. package/scripts/sync-skill-version.js +21 -21
  25. package/scripts/validate-skill.js +103 -103
  26. package/skills/trackops/SKILL.md +126 -122
  27. package/skills/trackops/agents/openai.yaml +7 -7
  28. package/skills/trackops/locales/en/SKILL.md +126 -122
  29. package/skills/trackops/locales/en/references/activation.md +94 -75
  30. package/skills/trackops/locales/en/references/troubleshooting.md +73 -55
  31. package/skills/trackops/locales/en/references/workflow.md +55 -32
  32. package/skills/trackops/references/activation.md +94 -75
  33. package/skills/trackops/references/troubleshooting.md +73 -55
  34. package/skills/trackops/references/workflow.md +55 -32
  35. package/skills/trackops/skill.json +29 -29
  36. package/templates/hooks/post-checkout +2 -2
  37. package/templates/hooks/post-commit +2 -2
  38. package/templates/hooks/post-merge +2 -2
  39. package/templates/opera/agent.md +28 -27
  40. package/templates/opera/architecture/dependency-graph.md +24 -24
  41. package/templates/opera/architecture/runtime-automation.md +24 -24
  42. package/templates/opera/architecture/runtime-operations.md +34 -34
  43. package/templates/opera/en/agent.md +22 -21
  44. package/templates/opera/en/architecture/dependency-graph.md +24 -24
  45. package/templates/opera/en/architecture/runtime-automation.md +24 -24
  46. package/templates/opera/en/architecture/runtime-operations.md +34 -34
  47. package/templates/opera/en/reviews/delivery-audit.md +18 -18
  48. package/templates/opera/en/reviews/integration-audit.md +18 -18
  49. package/templates/opera/en/router.md +24 -19
  50. package/templates/opera/references/autonomy-and-recovery.md +117 -117
  51. package/templates/opera/references/opera-cycle.md +193 -193
  52. package/templates/opera/registry.md +28 -28
  53. package/templates/opera/reviews/delivery-audit.md +18 -18
  54. package/templates/opera/reviews/integration-audit.md +18 -18
  55. package/templates/opera/router.md +54 -49
  56. package/templates/skills/changelog-updater/SKILL.md +69 -69
  57. package/templates/skills/commiter/SKILL.md +99 -99
  58. package/templates/skills/opera-contract-auditor/SKILL.md +38 -38
  59. package/templates/skills/opera-contract-auditor/locales/en/SKILL.md +38 -38
  60. package/templates/skills/opera-policy-guard/SKILL.md +26 -26
  61. package/templates/skills/opera-policy-guard/locales/en/SKILL.md +26 -26
  62. package/templates/skills/opera-skill/SKILL.md +279 -0
  63. package/templates/skills/opera-skill/locales/en/SKILL.md +279 -0
  64. package/templates/skills/opera-skill/locales/en/references/phase-dod.md +138 -0
  65. package/templates/skills/opera-skill/references/phase-dod.md +138 -0
  66. package/templates/skills/project-starter-skill/SKILL.md +150 -131
  67. package/templates/skills/project-starter-skill/locales/en/SKILL.md +143 -105
  68. package/templates/skills/project-starter-skill/references/opera-cycle.md +195 -193
  69. package/ui/css/base.css +284 -266
  70. package/ui/css/charts.css +425 -327
  71. package/ui/css/components.css +1107 -570
  72. package/ui/css/onboarding.css +133 -0
  73. package/ui/css/panels.css +345 -406
  74. package/ui/css/terminal.css +125 -0
  75. package/ui/css/timeline.css +58 -0
  76. package/ui/css/tokens.css +284 -227
  77. package/ui/favicon.svg +5 -5
  78. package/ui/index.html +99 -96
  79. package/ui/js/api.js +49 -13
  80. package/ui/js/app.js +28 -32
  81. package/ui/js/charts.js +526 -0
  82. package/ui/js/console-logger.js +172 -172
  83. package/ui/js/filters.js +247 -0
  84. package/ui/js/icons.js +129 -104
  85. package/ui/js/keyboard.js +229 -0
  86. package/ui/js/onboarding.js +33 -42
  87. package/ui/js/router.js +142 -125
  88. package/ui/js/theme.js +100 -100
  89. package/ui/js/time-tracker.js +248 -248
  90. package/ui/js/views/board.js +84 -114
  91. package/ui/js/views/dashboard.js +870 -0
  92. package/ui/js/views/flash.js +47 -47
  93. package/ui/js/views/projects.js +745 -0
  94. package/ui/js/views/scrum.js +476 -0
  95. package/ui/js/views/settings.js +153 -203
  96. package/ui/js/views/sidebar.js +37 -31
  97. package/ui/js/views/tasks.js +218 -101
  98. package/ui/js/views/timeline.js +265 -0
  99. package/ui/js/views/topbar.js +94 -107
  100. package/ui/app.js +0 -950
  101. package/ui/js/views/insights.js +0 -340
  102. package/ui/js/views/overview.js +0 -369
  103. package/ui/styles.css +0 -688
@@ -1,340 +0,0 @@
1
- /**
2
- * insights.js — Vista de Analytics: progreso, actividad, hallazgos, KPI de salud
3
- */
4
-
5
- import { icon } from '../icons.js';
6
- import * as state from '../state.js';
7
- import * as timeTracker from '../time-tracker.js';
8
- import { esc, formatDate, formatDurationShort, extractHistory } from '../utils.js';
9
- import { t } from '../i18n.js';
10
-
11
- export async function render() {
12
- const payload = state.getPayload();
13
- if (!payload) return `<div class="empty-state" style="margin:3rem">${t('ui.insights.noData', {}, 'No project data.')}</div>`;
14
-
15
- const { derived, control, runtime } = payload;
16
- const statusLabels = state.getStatusLabels();
17
- const history = extractHistory(control.tasks).slice(0, 20);
18
-
19
- return `
20
- <div class="view-enter">
21
- <div class="section-header">
22
- <div class="section-header-left">
23
- <p class="eyebrow">${t('ui.insights.eyebrow', {}, 'Insights')}</p>
24
- <h2>${t('ui.insights.title', {}, 'Project insights')}</h2>
25
- </div>
26
- </div>
27
-
28
- <!-- Fila 1: Health Grid + Distribution -->
29
- <div class="grid-2" style="margin-bottom:var(--space-5)">
30
-
31
- <!-- KPI de salud -->
32
- <div class="chart-card stagger-1">
33
- <div class="section-header" style="margin-bottom:var(--space-3)">
34
- <p class="chart-title">${t('ui.insights.health', {}, 'Operational health')}</p>
35
- </div>
36
- ${_renderHealthGrid(derived, runtime, payload.docsDirty)}
37
- </div>
38
-
39
- <!-- Distribución por estado -->
40
- <div class="chart-card stagger-2">
41
- <p class="chart-title" style="margin-bottom:var(--space-4)">${t('ui.insights.distribution', {}, 'Distribution by status')}</p>
42
- ${_renderDistribution(derived.totals, statusLabels)}
43
- </div>
44
-
45
- </div>
46
-
47
- <!-- Fila 2: Time entries + Phase progress -->
48
- <div class="grid-2" style="margin-bottom:var(--space-5)">
49
-
50
- <!-- Time tracking summary -->
51
- <div class="chart-card stagger-3">
52
- <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4)">
53
- <p class="chart-title">${t('ui.insights.time', {}, 'Time tracking')}</p>
54
- <span class="badge badge-accent">${icon('clock', 12)} ${t('ui.insights.today', {}, 'Today')}</span>
55
- </div>
56
- ${_renderTimeTracking()}
57
- </div>
58
-
59
- <!-- Phase progress detallado -->
60
- <div class="chart-card stagger-4">
61
- <p class="chart-title" style="margin-bottom:var(--space-4)">${t('ui.insights.phaseProgress', {}, 'Progress by phase')}</p>
62
- ${_renderPhaseProgress(derived.phaseStats)}
63
- </div>
64
-
65
- </div>
66
-
67
- <!-- Fila 3: Activity + Findings -->
68
- <div class="grid-2">
69
-
70
- <!-- Activity timeline -->
71
- <div class="chart-card stagger-1">
72
- <p class="chart-title" style="margin-bottom:var(--space-4)">${t('ui.insights.recentActivity', {}, 'Recent activity')}</p>
73
- ${_renderActivityTimeline(history, statusLabels)}
74
- </div>
75
-
76
- <!-- Hallazgos + Decisiones -->
77
- <div style="display:flex;flex-direction:column;gap:var(--space-4)">
78
-
79
- <!-- Findings -->
80
- <div class="chart-card stagger-2">
81
- <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-4)">
82
- <p class="chart-title">${t('ui.insights.findings', {}, 'Findings')}</p>
83
- <span class="badge badge-${derived.openFindings?.length ? 'warning' : 'success'}">
84
- ${t('ui.insights.openCount', { count: (derived.openFindings || []).length }, `${(derived.openFindings || []).length} open`)}
85
- </span>
86
- </div>
87
- ${_renderFindings(derived.openFindings || [], derived.resolvedFindings || [])}
88
- </div>
89
-
90
- <!-- Decisiones pendientes -->
91
- ${(control.decisionsPending || []).length > 0 ? `
92
- <div class="chart-card stagger-3">
93
- <p class="chart-title" style="margin-bottom:var(--space-4)">${t('ui.insights.pendingDecisions', {}, 'Pending decisions')}</p>
94
- ${_renderDecisions(control.decisionsPending)}
95
- </div>
96
- ` : ''}
97
-
98
- </div>
99
- </div>
100
- </div>
101
- `;
102
- }
103
-
104
- // ─────────────────────────────── HEALTH ─────────────────────────────────────
105
-
106
- function _renderHealthGrid(derived, runtime, docsDirty) {
107
- const totals = derived.totals;
108
- const completionRate = totals.all ? Math.round((totals.completed / totals.all) * 100) : 0;
109
- const blockerRate = totals.all ? Math.round((totals.blocked / totals.all) * 100) : 0;
110
- const openFindings = (derived.openFindings || []).length;
111
-
112
- const items = [
113
- { label: 'Tasa completada', value: `${completionRate}%`, cls: completionRate >= 75 ? 'good' : completionRate >= 40 ? '' : 'bad' },
114
- { label: 'Presión de bloqueos',value: `${blockerRate}%`, cls: blockerRate === 0 ? 'good' : blockerRate > 20 ? 'bad' : 'warn' },
115
- { label: 'En Progreso', value: String(totals.inProgress),cls: totals.inProgress > 0 ? 'good' : '' },
116
- { label: 'En Revisión', value: String(totals.inReview), cls: '' },
117
- { label: 'Hallazgos abiertos', value: String(openFindings), cls: openFindings === 0 ? 'good' : 'warn' },
118
- { label: 'Desfase documental', value: (docsDirty || []).length ? `${(docsDirty).length} archivos` : 'OK', cls: docsDirty?.length ? 'warn' : 'good' },
119
- { label: 'Por delante remoto', value: String(runtime?.ahead || 0), cls: runtime?.ahead > 0 ? 'warn' : 'good' },
120
- { label: 'Por detrás remoto', value: String(runtime?.behind || 0), cls: runtime?.behind > 0 ? 'bad' : 'good' },
121
- ];
122
-
123
- return `
124
- <div class="health-grid">
125
- ${items.map(({ label, value, cls }) => `
126
- <div class="health-card">
127
- <p class="health-card-label">${esc(label)}</p>
128
- <p class="health-card-value ${cls}" title="${esc(value)}">${esc(value)}</p>
129
- </div>
130
- `).join('')}
131
- </div>
132
- `;
133
- }
134
-
135
- // ─────────────────────────────── DISTRIBUCIÓN ───────────────────────────────
136
-
137
- function _renderDistribution(totals, statusLabels) {
138
- const all = totals.all || 1;
139
- const rows = [
140
- { id: 'completed', label: statusLabels.completed || 'Completado', value: totals.completed, cls: 'fill-success' },
141
- { id: 'in_progress',label: statusLabels.in_progress|| 'En progreso', value: totals.inProgress, cls: '' },
142
- { id: 'in_review', label: statusLabels.in_review || 'En revisión', value: totals.inReview, cls: '' },
143
- { id: 'pending', label: statusLabels.pending || 'Pendiente', value: totals.pending, cls: 'fill-warning' },
144
- { id: 'blocked', label: statusLabels.blocked || 'Bloqueado', value: totals.blocked, cls: 'fill-danger' },
145
- { id: 'cancelled', label: statusLabels.cancelled || 'Cancelado', value: totals.cancelled, cls: '' },
146
- ].filter(r => r.value > 0);
147
-
148
- return `
149
- <div class="bar-chart">
150
- ${rows.map(r => {
151
- const pct = Math.max(2, Math.round((r.value / all) * 100));
152
- return `
153
- <div class="bar-row" role="group" aria-label="${r.label}: ${r.value} (${pct}%)">
154
- <span class="bar-label truncate">${esc(r.label)}</span>
155
- <div class="bar-track" role="progressbar" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100">
156
- <div class="bar-fill ${r.cls}" style="width:${pct}%"></div>
157
- </div>
158
- <span class="bar-value">${r.value}</span>
159
- </div>
160
- `;
161
- }).join('')}
162
- </div>
163
- `;
164
- }
165
-
166
- // ─────────────────────────────── TIME TRACKING ──────────────────────────────
167
-
168
- function _renderTimeTracking() {
169
- const entries = state.get('timeEntries');
170
-
171
- if (!entries.length) {
172
- return `
173
- <div class="empty-state" style="padding:var(--space-6)">
174
- ${icon('clock', 24)}
175
- <p>No hay registros de tiempo aún.</p>
176
- <p class="text-muted" style="font-size:var(--text-xs)">Usa el seguimiento de tiempo en Resumen para registrar tiempo por tarea.</p>
177
- </div>
178
- `;
179
- }
180
-
181
- // Agrupar por tarea
182
- const byTask = new Map();
183
- for (const e of entries) {
184
- if (!byTask.has(e.taskId)) byTask.set(e.taskId, { taskId: e.taskId, taskTitle: e.taskTitle, total: 0, count: 0 });
185
- const rec = byTask.get(e.taskId);
186
- rec.total += e.durationMs || 0;
187
- rec.count++;
188
- }
189
-
190
- const maxMs = Math.max(...[...byTask.values()].map(r => r.total), 1);
191
-
192
- return `
193
- <div class="bar-chart">
194
- ${[...byTask.values()].slice(0, 10).map(r => {
195
- const pct = Math.max(4, Math.round((r.total / maxMs) * 100));
196
- return `
197
- <div class="bar-row">
198
- <span class="bar-label truncate" title="${esc(r.taskTitle)}">${esc(r.taskTitle)}</span>
199
- <div class="bar-track">
200
- <div class="bar-fill fill-info" style="width:${pct}%"></div>
201
- </div>
202
- <span class="bar-value">${formatDurationShort(r.total)}</span>
203
- </div>
204
- `;
205
- }).join('')}
206
- </div>
207
- <p class="text-muted" style="font-size:var(--text-xs);margin-top:var(--space-3)">
208
- ${entries.length} registros en total
209
- </p>
210
- `;
211
- }
212
-
213
- // ─────────────────────────────── PHASE PROGRESS ─────────────────────────────
214
-
215
- function _renderPhaseProgress(phaseStats) {
216
- if (!phaseStats?.length) {
217
- return '<p class="text-muted" style="font-size:var(--text-sm)">Sin fases configuradas.</p>';
218
- }
219
- return `
220
- <div class="phase-chart">
221
- ${phaseStats.map(p => {
222
- const pct = p.total ? Math.round((p.completed / p.total) * 100) : 0;
223
- const cls = pct === 100 ? 'done' : pct > 0 ? 'active' : 'partial';
224
- return `
225
- <div class="phase-row">
226
- <div class="phase-row-header">
227
- <div>
228
- <span class="phase-name">${esc(p.id)} · ${esc(p.label)}</span>
229
- </div>
230
- <span class="phase-progress">${p.completed}/${p.total} (${pct}%)</span>
231
- </div>
232
- <div class="phase-track" role="progressbar" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100">
233
- <div class="phase-fill ${cls}" style="width:${pct}%"></div>
234
- </div>
235
- <div style="display:flex;gap:var(--space-2);margin-top:var(--space-2);flex-wrap:wrap">
236
- ${p.remaining === 0
237
- ? `<span class="badge badge-success">Completada</span>`
238
- : `<span class="badge badge-muted">${p.remaining} pendientes</span>`}
239
- </div>
240
- </div>
241
- `;
242
- }).join('')}
243
- </div>
244
- `;
245
- }
246
-
247
- // ─────────────────────────────── ACTIVITY ───────────────────────────────────
248
-
249
- function _renderActivityTimeline(history, statusLabels) {
250
- if (!history.length) {
251
- return '<div class="empty-state" style="padding:var(--space-5)">Sin actividad reciente.</div>';
252
- }
253
-
254
- const actionIcon = {
255
- create: 'plus',
256
- start: 'play',
257
- review: 'alertCircle',
258
- complete: 'checkCircle',
259
- block: 'shield',
260
- pending: 'clock',
261
- cancel: 'x',
262
- note: 'fileText',
263
- edit: 'edit',
264
- };
265
-
266
- const actionColor = {
267
- create: 'var(--accent)',
268
- start: 'var(--info)',
269
- review: 'var(--warning)',
270
- complete: 'var(--success)',
271
- block: 'var(--danger)',
272
- cancel: 'var(--text-muted)',
273
- note: 'var(--accent)',
274
- edit: 'var(--text-secondary)',
275
- pending: 'var(--warning)',
276
- };
277
-
278
- return `
279
- <div class="stack" style="max-height:420px;overflow-y:auto">
280
- ${history.map(h => {
281
- const ic = actionIcon[h.action] || 'info';
282
- const col = actionColor[h.action] || 'var(--accent)';
283
- return `
284
- <div class="activity-item">
285
- <div class="activity-icon" style="color:${col}">
286
- ${icon(ic, 16)}
287
- </div>
288
- <div class="activity-content">
289
- <p class="activity-action">${esc(h.action)}${h.note ? ` — ${esc(h.note)}` : ''}</p>
290
- <p class="activity-task">${esc(h.taskTitle)} (${esc(h.taskId)})</p>
291
- <p class="activity-time">${formatDate(h.at)}</p>
292
- </div>
293
- </div>
294
- `;
295
- }).join('')}
296
- </div>
297
- `;
298
- }
299
-
300
- // ─────────────────────────────── FINDINGS ───────────────────────────────────
301
-
302
- function _renderFindings(open, resolved) {
303
- if (!open.length && !resolved.length) {
304
- return `<div class="empty-state" style="padding:var(--space-4)">${icon('checkCircle', 20)} Sin hallazgos.</div>`;
305
- }
306
-
307
- return `
308
- <div class="stack stack-sm">
309
- ${open.map(f => `
310
- <div class="finding-item severity-${(f.severity || 'medium').toLowerCase()}">
311
- <div style="display:flex;align-items:center;justify-content:space-between;gap:var(--space-2);margin-bottom:var(--space-1)">
312
- <p style="font-size:var(--text-sm);font-weight:700">${esc(f.title)}</p>
313
- <span class="badge badge-warning">${esc(f.severity?.toUpperCase() || 'MEDIUM')}</span>
314
- </div>
315
- <p style="font-size:var(--text-xs);color:var(--text-secondary)">${esc(f.detail || '')}</p>
316
- ${f.impact ? `<p style="font-size:var(--text-xs);color:var(--text-muted);margin-top:var(--space-1)">Impacto: ${esc(f.impact)}</p>` : ''}
317
- </div>
318
- `).join('')}
319
- ${resolved.length > 0 ? `<p class="label-sm" style="margin-top:var(--space-3)">${resolved.length} resueltos</p>` : ''}
320
- </div>
321
- `;
322
- }
323
-
324
- // ─────────────────────────────── DECISIONES ─────────────────────────────────
325
-
326
- function _renderDecisions(decisions) {
327
- return `
328
- <div class="stack stack-sm">
329
- ${decisions.map(d => `
330
- <div class="decision-item">
331
- <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-1)">
332
- <p style="font-size:var(--text-sm);font-weight:700">${esc(d.title)}</p>
333
- <span class="badge badge-muted">${esc(d.owner)}</span>
334
- </div>
335
- <p style="font-size:var(--text-xs);color:var(--text-secondary)">${esc(d.impact || '')}</p>
336
- </div>
337
- `).join('')}
338
- </div>
339
- `;
340
- }