trackops 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +292 -272
  2. package/bin/trackops.js +108 -50
  3. package/lib/config.js +267 -38
  4. package/lib/control.js +534 -480
  5. package/lib/env.js +244 -0
  6. package/lib/i18n.js +61 -53
  7. package/lib/init.js +170 -47
  8. package/lib/locale.js +63 -0
  9. package/lib/opera-bootstrap.js +1075 -0
  10. package/lib/opera.js +524 -125
  11. package/lib/preferences.js +74 -0
  12. package/lib/registry.js +27 -13
  13. package/lib/release.js +56 -0
  14. package/lib/resources.js +42 -0
  15. package/lib/runtime-state.js +144 -0
  16. package/lib/server.js +1004 -521
  17. package/lib/skills.js +148 -124
  18. package/lib/workspace.js +260 -0
  19. package/locales/en.json +418 -132
  20. package/locales/es.json +418 -132
  21. package/package.json +8 -9
  22. package/scripts/postinstall-locale.js +21 -0
  23. package/scripts/skills-marketplace-smoke.js +124 -0
  24. package/scripts/smoke-tests.js +570 -0
  25. package/scripts/sync-skill-version.js +21 -0
  26. package/scripts/validate-skill.js +89 -0
  27. package/skills/trackops/SKILL.md +89 -0
  28. package/skills/trackops/agents/openai.yaml +3 -0
  29. package/skills/trackops/references/activation.md +73 -0
  30. package/skills/trackops/references/troubleshooting.md +49 -0
  31. package/skills/trackops/references/workflow.md +26 -0
  32. package/skills/trackops/scripts/bootstrap-trackops.js +203 -0
  33. package/skills/trackops/skill.json +29 -0
  34. package/templates/opera/agent.md +10 -9
  35. package/templates/opera/architecture/dependency-graph.md +24 -0
  36. package/templates/opera/architecture/runtime-automation.md +24 -0
  37. package/templates/opera/architecture/runtime-operations.md +34 -0
  38. package/templates/opera/en/agent.md +27 -0
  39. package/templates/opera/en/architecture/dependency-graph.md +24 -0
  40. package/templates/opera/en/architecture/runtime-automation.md +24 -0
  41. package/templates/opera/en/architecture/runtime-operations.md +34 -0
  42. package/templates/opera/en/genesis.md +79 -0
  43. package/templates/opera/en/references/autonomy-and-recovery.md +23 -0
  44. package/templates/opera/en/references/opera-cycle.md +62 -0
  45. package/templates/opera/en/registry.md +28 -0
  46. package/templates/opera/en/reviews/delivery-audit.md +18 -0
  47. package/templates/opera/en/reviews/integration-audit.md +18 -0
  48. package/templates/opera/en/router.md +49 -0
  49. package/templates/opera/genesis.md +79 -94
  50. package/templates/opera/reviews/delivery-audit.md +18 -0
  51. package/templates/opera/reviews/integration-audit.md +18 -0
  52. package/templates/opera/router.md +15 -5
  53. package/templates/skills/changelog-updater/locales/en/SKILL.md +11 -0
  54. package/templates/skills/commiter/locales/en/SKILL.md +11 -0
  55. package/templates/skills/opera-contract-auditor/SKILL.md +38 -0
  56. package/templates/skills/opera-contract-auditor/locales/en/SKILL.md +38 -0
  57. package/templates/skills/opera-policy-guard/SKILL.md +26 -0
  58. package/templates/skills/opera-policy-guard/locales/en/SKILL.md +26 -0
  59. package/templates/skills/project-starter-skill/SKILL.md +89 -164
  60. package/templates/skills/project-starter-skill/locales/en/SKILL.md +104 -0
  61. package/ui/css/panels.css +956 -953
  62. package/ui/index.html +1 -1
  63. package/ui/js/api.js +211 -194
  64. package/ui/js/app.js +200 -199
  65. package/ui/js/i18n.js +14 -0
  66. package/ui/js/onboarding.js +439 -437
  67. package/ui/js/state.js +130 -129
  68. package/ui/js/utils.js +175 -172
  69. package/ui/js/views/board.js +255 -254
  70. package/ui/js/views/execution.js +256 -256
  71. package/ui/js/views/insights.js +340 -339
  72. package/ui/js/views/overview.js +366 -361
  73. package/ui/js/views/settings.js +340 -202
  74. package/ui/js/views/sidebar.js +131 -132
  75. package/ui/js/views/skills.js +163 -162
  76. package/ui/js/views/tasks.js +406 -405
  77. package/ui/js/views/topbar.js +239 -183
  78. package/templates/etapa/agent.md +0 -26
  79. package/templates/etapa/genesis.md +0 -94
  80. package/templates/etapa/references/autonomy-and-recovery.md +0 -117
  81. package/templates/etapa/references/etapa-cycle.md +0 -193
  82. package/templates/etapa/registry.md +0 -28
  83. package/templates/etapa/router.md +0 -39
package/ui/js/app.js CHANGED
@@ -1,199 +1,200 @@
1
- /**
2
- * app.js — Orquestador principal del dashboard TrackOps
3
- * Punto de entrada del módulo ES.
4
- */
5
-
6
- import * as state from './state.js';
7
- import * as api from './api.js';
8
- import * as router from './router.js';
9
- import * as consoleLogger from './console-logger.js';
10
- import * as onboarding from './onboarding.js';
11
- import * as timeTracker from './time-tracker.js';
12
- import * as theme from './theme.js';
13
-
14
- // Vistas
15
- import { render as renderSidebar } from './views/sidebar.js';
16
- import { render as renderTopbar } from './views/topbar.js';
17
- import { render as renderOverview } from './views/overview.js';
18
- import { render as renderBoard } from './views/board.js';
19
- import { render as renderTasks } from './views/tasks.js';
20
- import * as executionView from './views/execution.js';
21
- import { render as renderInsights } from './views/insights.js';
22
- import * as settingsView from './views/settings.js';
23
- import * as skillsView from './views/skills.js';
24
-
25
- // ─────────────────────────────── INIT ───────────────────────────────────────
26
-
27
- async function init() {
28
- // 0. Tema (PRIMERO: evitar flash de tema incorrecto)
29
- theme.init();
30
-
31
- // 1. Console logger (capturar errores desde el arranque)
32
- consoleLogger.init();
33
-
34
- // 2. Registrar rutas en el router
35
- router.register('overview', renderOverview);
36
- router.register('board', renderBoard);
37
- router.register('tasks', renderTasks);
38
- router.register('execution', async () => {
39
- const html = await executionView.render();
40
- setTimeout(() => executionView.bindEvents(), 50);
41
- return html;
42
- });
43
- router.register('insights', renderInsights);
44
- router.register('settings', async () => {
45
- const html = await settingsView.render();
46
- setTimeout(() => settingsView.bindEvents(), 50);
47
- return html;
48
- });
49
- router.register('skills', async () => {
50
- const html = await skillsView.render();
51
- setTimeout(() => {
52
- skillsView.bindEvents();
53
- skillsView.loadData();
54
- }, 50);
55
- return html;
56
- });
57
-
58
- // 3. Inicializar el router
59
- router.init(document.getElementById('view-container'));
60
-
61
- // 4. Cargar estado inicial
62
- await _loadInitialState();
63
-
64
- // 5. Renderizar chrome (sidebar + topbar)
65
- renderSidebar();
66
- renderTopbar();
67
-
68
- // 6. Navegar a la vista inicial
69
- await router.start('overview');
70
-
71
- // 7. Cargar time entries en background
72
- timeTracker.loadEntries().catch(err => {
73
- console.warn('[app] No se pudieron cargar time entries:', err.message);
74
- });
75
-
76
- // 8. Inicializar onboarding
77
- onboarding.init();
78
-
79
- // 9. Suscribir refreshes globales
80
- _bindGlobalEvents();
81
-
82
- // 10. Auto-refresh cada 60s
83
- setInterval(_refreshState, 60_000);
84
- }
85
-
86
- // ─────────────────────────────── CARGA DE ESTADO ────────────────────────────
87
-
88
- async function _loadInitialState() {
89
- try {
90
- // Cargar lista de proyectos
91
- const projectsResult = await api.getProjects();
92
- const projects = projectsResult.projects || [];
93
- state.update('projects', projects);
94
-
95
- // Determinar proyecto activo (persistido o primero disponible)
96
- const saved = localStorage.getItem('ops-dashboard-project');
97
- const first = projects.find(p => p.available);
98
- const currentId = saved && projects.some(p => p.id === saved)
99
- ? saved
100
- : first?.id || null;
101
-
102
- state.update('currentProjectId', currentId);
103
-
104
- // Cargar el estado del proyecto activo
105
- if (currentId) {
106
- await _refreshState();
107
- }
108
- } catch (err) {
109
- console.error('[app] Error cargando estado inicial:', err);
110
- // Si el endpoint de proyectos falla, intentar cargar el estado directamente
111
- try {
112
- await _refreshState();
113
- } catch (fallbackErr) {
114
- console.error('[app] Error en fallback de estado:', fallbackErr);
115
- }
116
- }
117
- }
118
-
119
- async function _refreshState() {
120
- try {
121
- const payload = await api.getState();
122
- state.update('payload', payload);
123
-
124
- // Actualizar datos de i18n desde el backend
125
- if (payload.i18n) {
126
- state.update('phases', payload.i18n.phases || []);
127
- state.update('statusLabels', payload.i18n.statusLabels || {});
128
- state.update('locale', payload.i18n.locale || 'es');
129
- }
130
-
131
- // Actualizar proyectos si el payload incluye info de proyectos
132
- if (payload.project && state.get('projects').length === 0) {
133
- state.update('projects', [{ ...payload.project, available: true }]);
134
- state.update('currentProjectId', payload.project.id);
135
- }
136
-
137
- // Actualizar helper findTask en el state (se accede via get('payload'))
138
- // NO se puede asignar a state.findTask porque los namespaces de módulos ES son readonly.
139
-
140
- // Re-renderizar chrome (puede haber cambiado el repo status)
141
- renderTopbar();
142
- renderSidebar();
143
-
144
- // Refrescar la vista actual
145
- await router.refresh();
146
-
147
- } catch (err) {
148
- console.error('[app] Error actualizando estado:', err);
149
- }
150
- }
151
-
152
- // ─────────────────────────────── EVENTOS GLOBALES ───────────────────────────
153
-
154
- function _bindGlobalEvents() {
155
- // Refresh global (disparado por sync, cambio de proyecto, etc.)
156
- window.addEventListener('ops:refresh', async () => {
157
- await _refreshState();
158
- });
159
-
160
- // Búsqueda global → refrescar la vista actual
161
- window.addEventListener('ops:search', () => {
162
- const active = router.current();
163
- if (active === 'board' || active === 'tasks') {
164
- router.refresh();
165
- }
166
- });
167
-
168
- // Navegación por teclado: Escape cierra modales / deselecciona
169
- document.addEventListener('keydown', e => {
170
- if (e.key === 'Escape') {
171
- // Deseleccionar tarea si no hay modal abierto
172
- const modalEl = document.querySelector('.modal-overlay:not(.is-hidden)');
173
- if (!modalEl) {
174
- // No deseleccionar: permite a las vistas manejar escape internamente
175
- }
176
- }
177
- });
178
-
179
- // Actualizar sidebar badges cuando cambia el payload
180
- state.subscribe('payload', () => {
181
- import('./views/sidebar.js').then(m => m.updateBadges?.());
182
- });
183
- }
184
-
185
- // ─────────────────────────────── ARRANQUE ───────────────────────────────────
186
-
187
- document.addEventListener('DOMContentLoaded', () => {
188
- init().catch(err => {
189
- console.error('[app] Error fatal en la inicialización:', err);
190
- document.getElementById('view-container').innerHTML = `
191
- <div class="empty-state" style="margin:4rem auto;max-width:440px">
192
- <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--danger)"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
193
- <h3 style="margin-top:var(--space-2)">Error al iniciar el dashboard</h3>
194
- <p style="font-size:var(--text-sm);color:var(--text-secondary)">${err.message}</p>
195
- <button class="btn btn-primary" onclick="location.reload()">Reintentar</button>
196
- </div>
197
- `;
198
- });
199
- });
1
+ /**
2
+ * app.js — Orquestador principal del dashboard TrackOps
3
+ * Punto de entrada del módulo ES.
4
+ */
5
+
6
+ import * as state from './state.js';
7
+ import * as api from './api.js';
8
+ import * as router from './router.js';
9
+ import * as consoleLogger from './console-logger.js';
10
+ import * as onboarding from './onboarding.js';
11
+ import * as timeTracker from './time-tracker.js';
12
+ import * as theme from './theme.js';
13
+
14
+ // Vistas
15
+ import { render as renderSidebar } from './views/sidebar.js';
16
+ import { render as renderTopbar } from './views/topbar.js';
17
+ import { render as renderOverview } from './views/overview.js';
18
+ import { render as renderBoard } from './views/board.js';
19
+ import { render as renderTasks } from './views/tasks.js';
20
+ import * as executionView from './views/execution.js';
21
+ import { render as renderInsights } from './views/insights.js';
22
+ import * as settingsView from './views/settings.js';
23
+ import * as skillsView from './views/skills.js';
24
+
25
+ // ─────────────────────────────── INIT ───────────────────────────────────────
26
+
27
+ async function init() {
28
+ // 0. Tema (PRIMERO: evitar flash de tema incorrecto)
29
+ theme.init();
30
+
31
+ // 1. Console logger (capturar errores desde el arranque)
32
+ consoleLogger.init();
33
+
34
+ // 2. Registrar rutas en el router
35
+ router.register('overview', renderOverview);
36
+ router.register('board', renderBoard);
37
+ router.register('tasks', renderTasks);
38
+ router.register('execution', async () => {
39
+ const html = await executionView.render();
40
+ setTimeout(() => executionView.bindEvents(), 50);
41
+ return html;
42
+ });
43
+ router.register('insights', renderInsights);
44
+ router.register('settings', async () => {
45
+ const html = await settingsView.render();
46
+ setTimeout(() => settingsView.bindEvents(), 50);
47
+ return html;
48
+ });
49
+ router.register('skills', async () => {
50
+ const html = await skillsView.render();
51
+ setTimeout(() => {
52
+ skillsView.bindEvents();
53
+ skillsView.loadData();
54
+ }, 50);
55
+ return html;
56
+ });
57
+
58
+ // 3. Inicializar el router
59
+ router.init(document.getElementById('view-container'));
60
+
61
+ // 4. Cargar estado inicial
62
+ await _loadInitialState();
63
+
64
+ // 5. Renderizar chrome (sidebar + topbar)
65
+ renderSidebar();
66
+ renderTopbar();
67
+
68
+ // 6. Navegar a la vista inicial
69
+ await router.start('overview');
70
+
71
+ // 7. Cargar time entries en background
72
+ timeTracker.loadEntries().catch(err => {
73
+ console.warn('[app] No se pudieron cargar time entries:', err.message);
74
+ });
75
+
76
+ // 8. Inicializar onboarding
77
+ onboarding.init();
78
+
79
+ // 9. Suscribir refreshes globales
80
+ _bindGlobalEvents();
81
+
82
+ // 10. Auto-refresh cada 60s
83
+ setInterval(_refreshState, 60_000);
84
+ }
85
+
86
+ // ─────────────────────────────── CARGA DE ESTADO ────────────────────────────
87
+
88
+ async function _loadInitialState() {
89
+ try {
90
+ // Cargar lista de proyectos
91
+ const projectsResult = await api.getProjects();
92
+ const projects = projectsResult.projects || [];
93
+ state.update('projects', projects);
94
+
95
+ // Determinar proyecto activo (persistido o primero disponible)
96
+ const saved = localStorage.getItem('ops-dashboard-project');
97
+ const first = projects.find(p => p.available);
98
+ const currentId = saved && projects.some(p => p.id === saved)
99
+ ? saved
100
+ : first?.id || null;
101
+
102
+ state.update('currentProjectId', currentId);
103
+
104
+ // Cargar el estado del proyecto activo
105
+ if (currentId) {
106
+ await _refreshState();
107
+ }
108
+ } catch (err) {
109
+ console.error('[app] Error cargando estado inicial:', err);
110
+ // Si el endpoint de proyectos falla, intentar cargar el estado directamente
111
+ try {
112
+ await _refreshState();
113
+ } catch (fallbackErr) {
114
+ console.error('[app] Error en fallback de estado:', fallbackErr);
115
+ }
116
+ }
117
+ }
118
+
119
+ async function _refreshState() {
120
+ try {
121
+ const payload = await api.getState();
122
+ state.update('payload', payload);
123
+
124
+ // Actualizar datos de i18n desde el backend
125
+ if (payload.i18n) {
126
+ state.update('phases', payload.i18n.phases || []);
127
+ state.update('statusLabels', payload.i18n.statusLabels || {});
128
+ state.update('locale', payload.i18n.locale || 'es');
129
+ state.update('messages', payload.i18n.messages || {});
130
+ }
131
+
132
+ // Actualizar proyectos si el payload incluye info de proyectos
133
+ if (payload.project && state.get('projects').length === 0) {
134
+ state.update('projects', [{ ...payload.project, available: true }]);
135
+ state.update('currentProjectId', payload.project.id);
136
+ }
137
+
138
+ // Actualizar helper findTask en el state (se accede via get('payload'))
139
+ // NO se puede asignar a state.findTask porque los namespaces de módulos ES son readonly.
140
+
141
+ // Re-renderizar chrome (puede haber cambiado el repo status)
142
+ renderTopbar();
143
+ renderSidebar();
144
+
145
+ // Refrescar la vista actual
146
+ await router.refresh();
147
+
148
+ } catch (err) {
149
+ console.error('[app] Error actualizando estado:', err);
150
+ }
151
+ }
152
+
153
+ // ─────────────────────────────── EVENTOS GLOBALES ───────────────────────────
154
+
155
+ function _bindGlobalEvents() {
156
+ // Refresh global (disparado por sync, cambio de proyecto, etc.)
157
+ window.addEventListener('ops:refresh', async () => {
158
+ await _refreshState();
159
+ });
160
+
161
+ // Búsqueda global → refrescar la vista actual
162
+ window.addEventListener('ops:search', () => {
163
+ const active = router.current();
164
+ if (active === 'board' || active === 'tasks') {
165
+ router.refresh();
166
+ }
167
+ });
168
+
169
+ // Navegación por teclado: Escape cierra modales / deselecciona
170
+ document.addEventListener('keydown', e => {
171
+ if (e.key === 'Escape') {
172
+ // Deseleccionar tarea si no hay modal abierto
173
+ const modalEl = document.querySelector('.modal-overlay:not(.is-hidden)');
174
+ if (!modalEl) {
175
+ // No deseleccionar: permite a las vistas manejar escape internamente
176
+ }
177
+ }
178
+ });
179
+
180
+ // Actualizar sidebar badges cuando cambia el payload
181
+ state.subscribe('payload', () => {
182
+ import('./views/sidebar.js').then(m => m.updateBadges?.());
183
+ });
184
+ }
185
+
186
+ // ─────────────────────────────── ARRANQUE ───────────────────────────────────
187
+
188
+ document.addEventListener('DOMContentLoaded', () => {
189
+ init().catch(err => {
190
+ console.error('[app] Error fatal en la inicialización:', err);
191
+ document.getElementById('view-container').innerHTML = `
192
+ <div class="empty-state" style="margin:4rem auto;max-width:440px">
193
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--danger)"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
194
+ <h3 style="margin-top:var(--space-2)">Error al iniciar el dashboard</h3>
195
+ <p style="font-size:var(--text-sm);color:var(--text-secondary)">${err.message}</p>
196
+ <button class="btn btn-primary" onclick="location.reload()">Reintentar</button>
197
+ </div>
198
+ `;
199
+ });
200
+ });
package/ui/js/i18n.js ADDED
@@ -0,0 +1,14 @@
1
+ import * as state from './state.js';
2
+
3
+ export function t(key, params = {}, fallback = null) {
4
+ const messages = state.get('messages') || {};
5
+ let value = messages[key] || fallback || key;
6
+ for (const [param, replacement] of Object.entries(params)) {
7
+ value = value.replace(new RegExp(`\\{${param}\\}`, 'g'), String(replacement));
8
+ }
9
+ return value;
10
+ }
11
+
12
+ export function listFormat(values, separator = ', ') {
13
+ return (values || []).filter(Boolean).join(separator);
14
+ }