trackops 2.0.3 → 2.0.4

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 (45) hide show
  1. package/README.md +238 -0
  2. package/lib/init.js +2 -2
  3. package/lib/locale.js +41 -17
  4. package/lib/opera-bootstrap.js +68 -7
  5. package/lib/opera.js +10 -2
  6. package/lib/registry.js +18 -0
  7. package/lib/server.js +312 -207
  8. package/locales/en.json +4 -0
  9. package/locales/es.json +4 -0
  10. package/package.json +1 -1
  11. package/skills/trackops/locales/en/references/activation.md +15 -0
  12. package/skills/trackops/locales/en/references/troubleshooting.md +12 -0
  13. package/skills/trackops/references/activation.md +15 -0
  14. package/skills/trackops/references/troubleshooting.md +12 -0
  15. package/skills/trackops/skill.json +2 -2
  16. package/ui/css/base.css +19 -1
  17. package/ui/css/charts.css +106 -8
  18. package/ui/css/components.css +554 -17
  19. package/ui/css/onboarding.css +133 -0
  20. package/ui/css/panels.css +345 -406
  21. package/ui/css/terminal.css +125 -0
  22. package/ui/css/timeline.css +58 -0
  23. package/ui/css/tokens.css +170 -113
  24. package/ui/index.html +3 -0
  25. package/ui/js/api.js +49 -13
  26. package/ui/js/app.js +28 -32
  27. package/ui/js/charts.js +526 -0
  28. package/ui/js/filters.js +247 -0
  29. package/ui/js/icons.js +82 -57
  30. package/ui/js/keyboard.js +229 -0
  31. package/ui/js/onboarding.js +33 -42
  32. package/ui/js/router.js +20 -3
  33. package/ui/js/views/board.js +84 -114
  34. package/ui/js/views/dashboard.js +870 -0
  35. package/ui/js/views/projects.js +745 -0
  36. package/ui/js/views/scrum.js +476 -0
  37. package/ui/js/views/settings.js +197 -247
  38. package/ui/js/views/sidebar.js +37 -31
  39. package/ui/js/views/tasks.js +218 -101
  40. package/ui/js/views/timeline.js +265 -0
  41. package/ui/js/views/topbar.js +94 -107
  42. package/ui/app.js +0 -950
  43. package/ui/js/views/insights.js +0 -340
  44. package/ui/js/views/overview.js +0 -369
  45. package/ui/styles.css +0 -688
package/README.md CHANGED
@@ -67,6 +67,125 @@ Esta separacion es intencional:
67
67
  - el runtime se instala con un paso visible y verificable
68
68
  - no hay instalacion transitiva oculta desde la propia skill
69
69
 
70
+ Si `npm install -g trackops` se ejecuta en modo interactivo, TrackOps intenta pedir el idioma global en ese momento. Si tu terminal o npm no muestran ese prompt, puedes fijarlo manualmente despues:
71
+
72
+ ```bash
73
+ trackops locale set es
74
+ trackops locale set en
75
+ ```
76
+
77
+ ### Flujo completo recomendado
78
+
79
+ 1. Instala la skill global:
80
+
81
+ ```bash
82
+ npx skills add Baxahaun/trackops --skill trackops --agent "*" --global -y
83
+ ```
84
+
85
+ 2. Instala el runtime:
86
+
87
+ ```bash
88
+ npm install -g trackops@latest
89
+ trackops --version
90
+ ```
91
+
92
+ 3. Entra en el repo que quieres gestionar:
93
+
94
+ ```bash
95
+ cd ruta/a/tu/proyecto
96
+ ```
97
+
98
+ 4. Activa TrackOps y elige el idioma del proyecto cuando el CLI lo pida:
99
+
100
+ ```bash
101
+ trackops init
102
+ ```
103
+
104
+ 5. Instala OPERA:
105
+
106
+ ```bash
107
+ trackops opera install
108
+ ```
109
+
110
+ 6. Responde el intake inicial con estos valores:
111
+
112
+ - nivel tecnico:
113
+ `low|medium|high|senior`
114
+ tambien acepta `bajo|medio|alto`
115
+ - estado del proyecto:
116
+ `idea|draft|existing_repo|advanced`
117
+ - documentacion:
118
+ `none|notes|sos|spec_dossier|repo_docs`
119
+ - propiedad de decision:
120
+ `user|shared|agent`
121
+ tambien acepta `usuario|compartido|agente`
122
+
123
+ 7. Si OPERA deriva al agente:
124
+
125
+ ```bash
126
+ trackops opera handoff --print
127
+ ```
128
+
129
+ Pega ese contexto en el agente, deja que genere:
130
+
131
+ - `ops/bootstrap/intake.json`
132
+ - `ops/bootstrap/spec-dossier.md`
133
+ - `ops/bootstrap/open-questions.md` si faltan decisiones
134
+
135
+ Y despues reanuda:
136
+
137
+ ```bash
138
+ trackops opera bootstrap --resume
139
+ ```
140
+
141
+ 8. Si OPERA completa bootstrap directo, revisa estado y continua con:
142
+
143
+ ```bash
144
+ trackops opera status
145
+ trackops next
146
+ trackops sync
147
+ ```
148
+
149
+ ### Desinstalacion global y local
150
+
151
+ #### Quitar la instalacion global
152
+
153
+ Quita la skill global del agente:
154
+
155
+ ```bash
156
+ npx skills remove --global trackops -y
157
+ ```
158
+
159
+ Quita el runtime global:
160
+
161
+ ```bash
162
+ npm uninstall -g trackops
163
+ ```
164
+
165
+ Verifica:
166
+
167
+ ```bash
168
+ npx skills ls -g
169
+ trackops --version
170
+ ```
171
+
172
+ #### Quitar TrackOps de un proyecto
173
+
174
+ Hoy no existe un comando `trackops uninstall` para el repo. La retirada local es manual.
175
+
176
+ En un workspace split, revisa y elimina solo lo que de verdad quieras retirar:
177
+
178
+ - `.trackops-workspace.json`
179
+ - `ops/`
180
+ - `app/.env` si era solo bridge
181
+
182
+ Revisa con cuidado antes de borrar:
183
+
184
+ - `/.env`
185
+ - `/.env.example`
186
+
187
+ Esos archivos pueden seguir siendo utiles para tu proyecto aunque dejes de usar TrackOps.
188
+
70
189
  ### Activacion local
71
190
 
72
191
  Dentro de un repo:
@@ -138,6 +257,8 @@ Si el usuario no es tecnico, el proyecto esta en fase idea, o no hay documentaci
138
257
  trackops opera bootstrap --resume
139
258
  ```
140
259
 
260
+ La terminal tambien debe decirte este siguiente paso al terminar el handoff.
261
+
141
262
  #### Ya tengo un repo
142
263
 
143
264
  Si el usuario es tecnico y el proyecto ya tiene suficiente contexto, OPERA sigue por bootstrap directo, compila `ops/contract/operating-contract.json` y recompila `ops/genesis.md`.
@@ -288,6 +409,83 @@ This split is intentional:
288
409
  - the runtime is installed through a visible and verifiable step
289
410
  - there is no hidden transitive install from the skill itself
290
411
 
412
+ If `npm install -g trackops` runs interactively, TrackOps tries to ask for the global language at that moment. If your terminal or npm do not show that prompt, set it manually afterwards:
413
+
414
+ ```bash
415
+ trackops locale set es
416
+ trackops locale set en
417
+ ```
418
+
419
+ ### Recommended full flow
420
+
421
+ 1. Install the global skill:
422
+
423
+ ```bash
424
+ npx skills add Baxahaun/trackops --skill trackops --agent "*" --global -y
425
+ ```
426
+
427
+ 2. Install the runtime:
428
+
429
+ ```bash
430
+ npm install -g trackops@latest
431
+ trackops --version
432
+ ```
433
+
434
+ 3. Enter the repository you want to manage:
435
+
436
+ ```bash
437
+ cd path/to/your/project
438
+ ```
439
+
440
+ 4. Activate TrackOps and choose the project language when the CLI asks:
441
+
442
+ ```bash
443
+ trackops init
444
+ ```
445
+
446
+ 5. Install OPERA:
447
+
448
+ ```bash
449
+ trackops opera install
450
+ ```
451
+
452
+ 6. Answer the initial intake with these values:
453
+
454
+ - technical level:
455
+ `low|medium|high|senior`
456
+ - project state:
457
+ `idea|draft|existing_repo|advanced`
458
+ - documentation:
459
+ `none|notes|sos|spec_dossier|repo_docs`
460
+ - decision ownership:
461
+ `user|shared|agent`
462
+
463
+ 7. If OPERA routes to the agent:
464
+
465
+ ```bash
466
+ trackops opera handoff --print
467
+ ```
468
+
469
+ Paste that context into the agent and let it generate:
470
+
471
+ - `ops/bootstrap/intake.json`
472
+ - `ops/bootstrap/spec-dossier.md`
473
+ - `ops/bootstrap/open-questions.md` when decisions are still missing
474
+
475
+ Then resume with:
476
+
477
+ ```bash
478
+ trackops opera bootstrap --resume
479
+ ```
480
+
481
+ 8. If OPERA completes direct bootstrap, review status and continue with:
482
+
483
+ ```bash
484
+ trackops opera status
485
+ trackops next
486
+ trackops sync
487
+ ```
488
+
291
489
  ### Local activation
292
490
 
293
491
  Inside a repository:
@@ -358,6 +556,46 @@ trackops opera install --bootstrap-mode handoff
358
556
  trackops opera install --bootstrap-mode direct
359
557
  ```
360
558
 
559
+ ### Global and local removal
560
+
561
+ #### Remove the global install
562
+
563
+ Remove the global skill from the agent:
564
+
565
+ ```bash
566
+ npx skills remove --global trackops -y
567
+ ```
568
+
569
+ Remove the global runtime:
570
+
571
+ ```bash
572
+ npm uninstall -g trackops
573
+ ```
574
+
575
+ Verify:
576
+
577
+ ```bash
578
+ npx skills ls -g
579
+ trackops --version
580
+ ```
581
+
582
+ #### Remove TrackOps from a project
583
+
584
+ There is no `trackops uninstall` command for the repository yet. Local removal is manual.
585
+
586
+ In a split workspace, review and remove only what you really want to retire:
587
+
588
+ - `.trackops-workspace.json`
589
+ - `ops/`
590
+ - `app/.env` if it was only the compatibility bridge
591
+
592
+ Review carefully before deleting:
593
+
594
+ - `/.env`
595
+ - `/.env.example`
596
+
597
+ Those files may still be useful to the project even if you stop using TrackOps.
598
+
361
599
  ### Environment and secrets
362
600
 
363
601
  TrackOps manages:
package/lib/init.js CHANGED
@@ -9,7 +9,7 @@ const registry = require("./registry");
9
9
  const env = require("./env");
10
10
  const workspace = require("./workspace");
11
11
  const { t, setLocale } = require("./i18n");
12
- const { detectSystemLocale, promptForLocale, resolveLocale } = require("./locale");
12
+ const { detectSystemLocale, promptForLocale, maybePromptForLocale, resolveLocale } = require("./locale");
13
13
  const runtimeState = require("./runtime-state");
14
14
 
15
15
  const GENERATED_SCRIPT_COMMANDS = {
@@ -299,7 +299,7 @@ async function cmdInit(args) {
299
299
  } else if (!globalLocale) {
300
300
  options.locale = await promptForLocale(detectSystemLocale());
301
301
  } else {
302
- options.locale = globalLocale;
302
+ options.locale = await maybePromptForLocale(globalLocale, { promptMode: "always" });
303
303
  }
304
304
  if (!globalLocale) {
305
305
  await runtimeState.ensureGlobalLocale({ preferredLocale: options.locale, interactive: false });
package/lib/locale.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const readline = require("readline/promises");
4
-
5
- const SUPPORTED_LOCALES = ["es", "en"];
3
+ const readline = require("readline/promises");
4
+
5
+ const SUPPORTED_LOCALES = ["es", "en"];
6
6
 
7
7
  function normalizeLocale(value) {
8
8
  const raw = String(value || "").trim().toLowerCase();
@@ -32,9 +32,9 @@ function isInteractive() {
32
32
  return Boolean(process.stdin.isTTY && process.stdout.isTTY);
33
33
  }
34
34
 
35
- async function promptForLocale(defaultLocale) {
36
- const initial = normalizeLocale(defaultLocale) || detectSystemLocale();
37
- if (!isInteractive()) return initial;
35
+ async function promptForLocale(defaultLocale) {
36
+ const initial = normalizeLocale(defaultLocale) || detectSystemLocale();
37
+ if (!isInteractive()) return initial;
38
38
 
39
39
  const rl = readline.createInterface({
40
40
  input: process.stdin,
@@ -46,18 +46,42 @@ async function promptForLocale(defaultLocale) {
46
46
  return normalizeLocale(answer) || initial;
47
47
  } finally {
48
48
  rl.close();
49
- }
50
- }
51
-
52
- function resolveLocale(preferred, fallback) {
53
- return normalizeLocale(preferred) || normalizeLocale(fallback) || detectSystemLocale();
54
- }
49
+ }
50
+ }
51
+
52
+ async function maybePromptForLocale(defaultLocale, options = {}) {
53
+ const initial = normalizeLocale(defaultLocale) || detectSystemLocale();
54
+ if (!isInteractive()) return initial;
55
+
56
+ const promptMode = String(options.promptMode || "always").trim().toLowerCase();
57
+ if (promptMode === "never") return initial;
58
+
59
+ const rl = readline.createInterface({
60
+ input: process.stdin,
61
+ output: process.stdout,
62
+ });
63
+
64
+ try {
65
+ const answer = await rl.question(
66
+ `Choose project language / Elige idioma del proyecto [es/en] (${initial}, Enter = keep): `,
67
+ );
68
+ const normalized = normalizeLocale(answer);
69
+ return normalized || initial;
70
+ } finally {
71
+ rl.close();
72
+ }
73
+ }
74
+
75
+ function resolveLocale(preferred, fallback) {
76
+ return normalizeLocale(preferred) || normalizeLocale(fallback) || detectSystemLocale();
77
+ }
55
78
 
56
79
  module.exports = {
57
80
  SUPPORTED_LOCALES,
58
81
  normalizeLocale,
59
- detectSystemLocale,
60
- isInteractive,
61
- promptForLocale,
62
- resolveLocale,
63
- };
82
+ detectSystemLocale,
83
+ isInteractive,
84
+ promptForLocale,
85
+ maybePromptForLocale,
86
+ resolveLocale,
87
+ };
@@ -35,6 +35,49 @@ const BOOTSTRAP_MODES = ["auto", "direct", "handoff"];
35
35
  const QUALITY_STATUSES = ["ready", "needs_review", "blocked"];
36
36
  const CONTRACT_READINESS = ["hypothesis", "provisional", "verified", "locked"];
37
37
  const CONTRACT_VERSION = 3;
38
+ const ENUM_ALIASES = {
39
+ low: "low",
40
+ bajo: "low",
41
+ basic: "low",
42
+ beginner: "low",
43
+ medium: "medium",
44
+ medio: "medium",
45
+ mid: "medium",
46
+ high: "high",
47
+ alto: "high",
48
+ advanced: "high",
49
+ senior: "senior",
50
+ idea: "idea",
51
+ ideacion: "idea",
52
+ ideation: "idea",
53
+ draft: "draft",
54
+ borrador: "draft",
55
+ existing_repo: "existing_repo",
56
+ existingrepo: "existing_repo",
57
+ existing: "existing_repo",
58
+ repo_existente: "existing_repo",
59
+ "repo existente": "existing_repo",
60
+ advanced_project: "advanced",
61
+ avanzado: "advanced",
62
+ advancedrepo: "advanced",
63
+ none: "none",
64
+ ninguna: "none",
65
+ notes: "notes",
66
+ notas: "notes",
67
+ sos: "sos",
68
+ spec_dossier: "spec_dossier",
69
+ spec: "spec_dossier",
70
+ dossier: "spec_dossier",
71
+ repo_docs: "repo_docs",
72
+ repodocs: "repo_docs",
73
+ docs_repo: "repo_docs",
74
+ user: "user",
75
+ usuario: "user",
76
+ shared: "shared",
77
+ compartido: "shared",
78
+ agent: "agent",
79
+ agente: "agent",
80
+ };
38
81
 
39
82
  function nowIso() {
40
83
  return new Date().toISOString();
@@ -81,8 +124,17 @@ function unique(items) {
81
124
  }
82
125
 
83
126
  function normalizeEnum(value, allowed) {
84
- const normalized = String(value || "").trim().toLowerCase();
85
- return allowed.includes(normalized) ? normalized : null;
127
+ const normalized = String(value || "").trim().toLowerCase().replace(/\s+/g, "_");
128
+ const alias = ENUM_ALIASES[normalized] || normalized;
129
+ return allowed.includes(alias) ? alias : null;
130
+ }
131
+
132
+ function choiceHint(allowed) {
133
+ if (allowed === TECHNICAL_LEVELS) return "low|medium|high|senior (o bajo|medio|alto)";
134
+ if (allowed === PROJECT_STATES) return "idea|draft|existing_repo|advanced";
135
+ if (allowed === DOC_STATES) return "none|notes|sos|spec_dossier|repo_docs";
136
+ if (allowed === DECISION_OWNERSHIPS) return "user|shared|agent (o usuario|compartido|agente)";
137
+ return Array.isArray(allowed) ? allowed.join("|") : null;
86
138
  }
87
139
 
88
140
  function explanationModeFor(technicalLevel) {
@@ -384,6 +436,14 @@ async function askQuestion(rl, message, defaultValue) {
384
436
  return String(answer || "").trim() || String(defaultValue || "").trim();
385
437
  }
386
438
 
439
+ async function askEnumQuestion(rl, message, defaultValue, allowed) {
440
+ const hint = choiceHint(allowed);
441
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
442
+ const answer = await rl.question(`${message}${hint ? ` [${hint}]` : ""}${suffix}: `);
443
+ const normalized = normalizeEnum(answer || defaultValue, allowed);
444
+ return normalized || normalizeEnum(defaultValue, allowed);
445
+ }
446
+
387
447
  async function collectBootstrapProfile(root, control, options = {}) {
388
448
  const context = config.ensureContext(root);
389
449
  const locale = config.getLocale(control);
@@ -427,15 +487,16 @@ async function collectBootstrapProfile(root, control, options = {}) {
427
487
  const answers = { ...defaults };
428
488
 
429
489
  if (interactive) {
430
- console.log("");
490
+ console.log("");
431
491
  console.log(t("bootstrap.header"));
432
492
  console.log(t("bootstrap.subtitle"));
493
+ console.log(t("bootstrap.instructions"));
433
494
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
434
495
  try {
435
- answers.technicalLevel = normalizeEnum(await askQuestion(rl, t("bootstrap.question.technicalLevel"), defaults.technicalLevel || "medium"), TECHNICAL_LEVELS) || "medium";
436
- answers.projectState = normalizeEnum(await askQuestion(rl, t("bootstrap.question.projectState"), defaults.projectState || "idea"), PROJECT_STATES) || "idea";
437
- answers.documentationState = normalizeEnum(await askQuestion(rl, t("bootstrap.question.docsState"), defaults.documentationState || "none"), DOC_STATES) || "none";
438
- answers.decisionOwnership = normalizeEnum(await askQuestion(rl, t("bootstrap.question.decisionOwnership"), defaults.decisionOwnership || "shared"), DECISION_OWNERSHIPS) || "shared";
496
+ answers.technicalLevel = await askEnumQuestion(rl, t("bootstrap.question.technicalLevel"), defaults.technicalLevel || "medium", TECHNICAL_LEVELS) || "medium";
497
+ answers.projectState = await askEnumQuestion(rl, t("bootstrap.question.projectState"), defaults.projectState || "idea", PROJECT_STATES) || "idea";
498
+ answers.documentationState = await askEnumQuestion(rl, t("bootstrap.question.docsState"), defaults.documentationState || "none", DOC_STATES) || "none";
499
+ answers.decisionOwnership = await askEnumQuestion(rl, t("bootstrap.question.decisionOwnership"), defaults.decisionOwnership || "shared", DECISION_OWNERSHIPS) || "shared";
439
500
  } finally {
440
501
  rl.close();
441
502
  }
package/lib/opera.js CHANGED
@@ -6,7 +6,7 @@ const path = require("path");
6
6
  const config = require("./config");
7
7
  const env = require("./env");
8
8
  const { t, setLocale } = require("./i18n");
9
- const { promptForLocale, resolveLocale } = require("./locale");
9
+ const { promptForLocale, maybePromptForLocale, resolveLocale } = require("./locale");
10
10
  const { resolveLocalizedFile, resolveLocalizedDir, resolveSkillFile } = require("./resources");
11
11
  const bootstrap = require("./opera-bootstrap");
12
12
  const runtimeState = require("./runtime-state");
@@ -188,8 +188,10 @@ async function install(root, options = {}) {
188
188
  if (!runtimeState.getGlobalLocale()) {
189
189
  await runtimeState.ensureGlobalLocale({ preferredLocale: locale, interactive: false });
190
190
  }
191
+ } else if (!options.locale) {
192
+ locale = await maybePromptForLocale(locale, { promptMode: "always" });
191
193
  }
192
- control.meta.locale = locale;
194
+ control.meta.locale = locale;
193
195
  setLocale(locale);
194
196
 
195
197
  const alreadyInstalled = config.isOperaInstalled(control);
@@ -313,8 +315,14 @@ async function runBootstrap(root, options = {}) {
313
315
  if (profile.mode === "agent_handoff") {
314
316
  console.log(t("bootstrap.awaitingAgent"));
315
317
  console.log(`${t("bootstrap.handoffFile")}: ${profile.handoffFiles?.markdown || bootstrap.bootstrapRelativePaths(context).markdown}`);
318
+ console.log(t("bootstrap.next.handoff"));
316
319
  } else {
317
320
  console.log(profile.status === "completed" ? t("bootstrap.completed") : t("bootstrap.pending"));
321
+ console.log(
322
+ profile.status === "completed"
323
+ ? t("bootstrap.next.directCompleted")
324
+ : t("bootstrap.next.directPending"),
325
+ );
318
326
  }
319
327
  return profile;
320
328
  }
package/lib/registry.js CHANGED
@@ -122,6 +122,22 @@ function unregisterProject(rootDir) {
122
122
  return registry;
123
123
  }
124
124
 
125
+ function unregisterById(projectId) {
126
+ const registry = loadRegistry();
127
+ registry.projects = registry.projects.filter((p) => p.id !== projectId);
128
+ saveRegistry(registry);
129
+ return registry;
130
+ }
131
+
132
+ function purgeUnavailable() {
133
+ const registry = loadRegistry();
134
+ const before = registry.projects.length;
135
+ registry.projects = registry.projects.filter((p) => isProjectInstalled(p.workspaceRoot || p.root));
136
+ const removed = before - registry.projects.length;
137
+ saveRegistry(registry);
138
+ return { removed, registry };
139
+ }
140
+
125
141
  function listProjects() {
126
142
  const registry = loadRegistry();
127
143
  return registry.projects.map((project) => {
@@ -185,10 +201,12 @@ module.exports = {
185
201
  isProjectInstalled,
186
202
  listProjects,
187
203
  loadRegistry,
204
+ purgeUnavailable,
188
205
  registerProject,
189
206
  resolveProject,
190
207
  saveRegistry,
191
208
  slugify,
209
+ unregisterById,
192
210
  unregisterProject,
193
211
  cmdRegister,
194
212
  cmdList,