trackops 2.0.5 → 2.0.6

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 CHANGED
@@ -98,11 +98,13 @@ trackops --version
98
98
  cd ruta/a/tu/proyecto
99
99
  ```
100
100
 
101
- 4. Activa TrackOps y elige el idioma del proyecto cuando el CLI lo pida:
102
-
103
- ```bash
104
- trackops init
105
- ```
101
+ 4. Activa TrackOps y elige el idioma del proyecto cuando el CLI lo pida:
102
+
103
+ ```bash
104
+ trackops init
105
+ ```
106
+
107
+ `trackops init` funciona sobre directorios vacios, repos ya existentes y workspaces ya activados. Si lo ejecutas otra vez, refresca los artefactos gestionados. `trackops workspace migrate` queda reservado para proyectos TrackOps legacy.
106
108
 
107
109
  5. Instala OPERA:
108
110
 
@@ -141,13 +143,15 @@ Y despues reanuda:
141
143
  trackops opera bootstrap --resume
142
144
  ```
143
145
 
144
- 8. Si OPERA completa bootstrap directo, revisa estado y continua con:
145
-
146
- ```bash
147
- trackops opera status
148
- trackops next
149
- trackops sync
150
- ```
146
+ 8. Si OPERA completa bootstrap directo, revisa estado y continua con:
147
+
148
+ ```bash
149
+ trackops opera status
150
+ trackops next
151
+ trackops sync
152
+ ```
153
+
154
+ Si necesitas una salida mas simple para lectores de pantalla, logs o terminales con soporte Unicode limitado, añade `--plain` o `--a11y` a cualquier comando, por ejemplo `trackops status --plain`.
151
155
 
152
156
  ### Desinstalacion global y local
153
157
 
@@ -198,16 +202,18 @@ trackops init
198
202
  trackops opera install
199
203
  ```
200
204
 
201
- Semantica:
202
-
203
- - `trackops init`
204
- activa TrackOps en el repo
205
- - `trackops opera install`
206
- anade OPERA cuando quieres el framework operativo completo
207
- - `trackops init --with-opera`
208
- existe como atajo
209
- - `trackops init --legacy-layout`
210
- existe solo por compatibilidad
205
+ Semantica:
206
+
207
+ - `trackops init`
208
+ activa o actualiza TrackOps en el repo, adopta repos existentes y puede reejecutarse con seguridad
209
+ - `trackops opera install`
210
+ anade OPERA cuando quieres el framework operativo completo
211
+ - `trackops init --with-opera`
212
+ existe como atajo
213
+ - `trackops init --legacy-layout`
214
+ existe solo por compatibilidad
215
+
216
+ `trackops workspace migrate` se mantiene solo para proyectos TrackOps legacy.
211
217
 
212
218
  ### Workspace split
213
219
 
@@ -262,23 +268,32 @@ trackops opera bootstrap --resume
262
268
 
263
269
  La terminal tambien debe decirte este siguiente paso al terminar el handoff.
264
270
 
265
- #### Ya tengo un repo
266
-
267
- 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`.
268
-
269
- Tambien puedes forzar el modo:
271
+ #### Ya tengo un repo
272
+
273
+ Si el usuario es tecnico y el proyecto ya tiene suficiente contexto, OPERA sigue por bootstrap directo. En ese modo crea o actualiza desde el primer paso:
274
+
275
+ - `ops/bootstrap/intake.json`
276
+ - `ops/bootstrap/spec-dossier.md`
277
+ - `ops/bootstrap/open-questions.md`
278
+ - `ops/bootstrap/quality-report.json`
279
+
280
+ Despues solo compila `ops/contract/operating-contract.json` y recompila `ops/genesis.md` cuando el bootstrap ya es consistente.
281
+
282
+ Tambien puedes forzar el modo:
270
283
 
271
284
  ```bash
272
285
  trackops opera install --bootstrap-mode handoff
273
286
  trackops opera install --bootstrap-mode direct
274
287
  ```
275
288
 
276
- Flags disponibles:
277
-
278
- - `--technical-level low|medium|high|senior`
279
- - `--project-state idea|draft|existing_repo|advanced`
280
- - `--docs-state none|notes|sos|spec_dossier|repo_docs`
281
- - `--decision-ownership user|shared|agent`
289
+ Flags disponibles:
290
+
291
+ - `--technical-level low|medium|high|senior`
292
+ - `--project-state idea|draft|existing_repo|advanced`
293
+ - `--docs-state none|notes|sos|spec_dossier|repo_docs`
294
+ - `--decision-ownership user|shared|agent`
295
+
296
+ En modo directo, `trackops opera handoff` no inventa un handoff de agente: muestra un resumen guiado de esos archivos y te dice exactamente que completar antes de reanudar.
282
297
 
283
298
  ### Entorno y secretos
284
299
 
@@ -319,12 +334,12 @@ trackops doctor locale
319
334
 
320
335
  | Comando | Descripcion |
321
336
  |---|---|
322
- | `trackops init [--with-opera] [--locale es\|en] [--name "..."] [--no-bootstrap] [--legacy-layout]` | Inicializa TrackOps |
337
+ | `trackops init [--with-opera] [--locale es\|en] [--name "..."] [--no-bootstrap] [--legacy-layout]` | Inicializa o actualiza TrackOps; adopta repos existentes |
323
338
  | `trackops status` | Muestra estado operativo |
324
339
  | `trackops next` | Muestra la siguiente cola priorizada |
325
340
  | `trackops sync` | Regenera docs operativos |
326
341
  | `trackops workspace status` | Muestra layout y roots |
327
- | `trackops workspace migrate` | Migra un proyecto legacy |
342
+ | `trackops workspace migrate` | Migra un proyecto TrackOps legacy |
328
343
  | `trackops env status` | Audita claves presentes y faltantes |
329
344
  | `trackops env sync` | Regenera `/.env`, `/.env.example` y el puente |
330
345
  | `trackops locale get\|set [es\|en]` | Lee o fija el idioma global |
@@ -334,9 +349,11 @@ trackops doctor locale
334
349
  | `trackops opera install [--bootstrap-mode ...] [--technical-level ...] [--project-state ...] [--docs-state ...] [--decision-ownership ...]` | Instala OPERA y decide la ruta de bootstrap |
335
350
  | `trackops opera bootstrap [--resume]` | Continua el bootstrap o ingiere el resultado del agente |
336
351
  | `trackops opera handoff [--print\|--json]` | Muestra el handoff listo para copiar al agente |
337
- | `trackops opera status` | Muestra estado de instalacion y bootstrap |
338
- | `trackops opera configure` | Reconfigura idioma o fases |
339
- | `trackops opera upgrade --stable [--reset]` | Reescribe artefactos gestionados a la version estable actual |
352
+ | `trackops opera status` | Muestra estado de instalacion y bootstrap |
353
+ | `trackops opera configure` | Reconfigura idioma o fases |
354
+ | `trackops opera upgrade --stable [--reset]` | Reescribe artefactos gestionados a la version estable actual |
355
+
356
+ Todos los comandos aceptan `--plain` y `--a11y` para una salida mas lineal y accesible.
340
357
 
341
358
  ### Skills del proyecto
342
359
 
@@ -463,11 +480,13 @@ trackops --version
463
480
  cd path/to/your/project
464
481
  ```
465
482
 
466
- 4. Activate TrackOps and choose the project language when the CLI asks:
467
-
468
- ```bash
469
- trackops init
470
- ```
483
+ 4. Activate TrackOps and choose the project language when the CLI asks:
484
+
485
+ ```bash
486
+ trackops init
487
+ ```
488
+
489
+ `trackops init` works on empty directories, existing repositories, and already initialized workspaces. Rerunning it refreshes managed artifacts. `trackops workspace migrate` is reserved for legacy TrackOps projects.
471
490
 
472
491
  5. Install OPERA:
473
492
 
@@ -504,13 +523,15 @@ Then resume with:
504
523
  trackops opera bootstrap --resume
505
524
  ```
506
525
 
507
- 8. If OPERA completes direct bootstrap, review status and continue with:
508
-
509
- ```bash
510
- trackops opera status
511
- trackops next
512
- trackops sync
513
- ```
526
+ 8. If OPERA completes direct bootstrap, review status and continue with:
527
+
528
+ ```bash
529
+ trackops opera status
530
+ trackops next
531
+ trackops sync
532
+ ```
533
+
534
+ If you need simpler output for screen readers, logs, or terminals with limited Unicode support, add `--plain` or `--a11y` to any command, for example `trackops status --plain`.
514
535
 
515
536
  ### Local activation
516
537
 
@@ -571,16 +592,25 @@ If the user is not technical, the project is still in idea stage, or documentati
571
592
  trackops opera bootstrap --resume
572
593
  ```
573
594
 
574
- #### I already have a repository
575
-
576
- If the user is technical and the project already has enough context, OPERA continues with direct bootstrap, compiles `ops/contract/operating-contract.json`, and recompiles `ops/genesis.md`.
577
-
578
- You can also force the mode:
595
+ #### I already have a repository
596
+
597
+ If the user is technical and the project already has enough context, OPERA continues with direct bootstrap. In that mode it creates or updates these files from the start:
598
+
599
+ - `ops/bootstrap/intake.json`
600
+ - `ops/bootstrap/spec-dossier.md`
601
+ - `ops/bootstrap/open-questions.md`
602
+ - `ops/bootstrap/quality-report.json`
603
+
604
+ It only compiles `ops/contract/operating-contract.json` and recompiles `ops/genesis.md` once bootstrap becomes consistent.
605
+
606
+ You can also force the mode:
579
607
 
580
608
  ```bash
581
- trackops opera install --bootstrap-mode handoff
582
- trackops opera install --bootstrap-mode direct
583
- ```
609
+ trackops opera install --bootstrap-mode handoff
610
+ trackops opera install --bootstrap-mode direct
611
+ ```
612
+
613
+ In direct mode, `trackops opera handoff` does not fake an agent handoff. It shows a guided summary of those files and the exact next step before resume.
584
614
 
585
615
  ### Global and local removal
586
616
 
@@ -684,9 +714,9 @@ trackops skill install <name>
684
714
  trackops skill remove <name>
685
715
  ```
686
716
 
687
- ### Main CLI
688
-
689
- Core and OPERA commands follow the same contract as the Spanish section above, including `trackops opera handoff`, `trackops opera bootstrap --resume`, and the explicit `npm install -g trackops` runtime step.
717
+ ### Main CLI
718
+
719
+ Core and OPERA commands follow the same contract as the Spanish section above, including `trackops opera handoff`, `trackops opera bootstrap --resume`, the explicit `npm install -g trackops` runtime step, and the global `--plain` / `--a11y` flags.
690
720
 
691
721
  ### Publishing
692
722
 
package/bin/trackops.js CHANGED
@@ -1,13 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const path = require("path");
4
- const config = require("../lib/config");
5
- const runtimeState = require("../lib/runtime-state");
6
- const { setLocale, t } = require("../lib/i18n");
7
- const pkg = require("../package.json");
3
+ const nodeVersion = parseInt(process.versions.node, 10);
4
+ if (nodeVersion < 18) {
5
+ console.error(`TrackOps requiere Node.js 18 o superior. Version actual: ${process.versions.node}`);
6
+ process.exit(1);
7
+ }
8
8
 
9
- const command = process.argv[2];
10
- const args = process.argv.slice(3);
9
+ const path = require("path");
10
+ const config = require("../lib/config");
11
+ const runtimeState = require("../lib/runtime-state");
12
+ const { setLocale, t } = require("../lib/i18n");
13
+ const fmt = require("../lib/cli-format");
14
+ const pkg = require("../package.json");
15
+
16
+ function parseCliTokens(argv) {
17
+ const plain = argv.includes("--plain") || argv.includes("--a11y");
18
+ const tokens = argv.filter((token) => !["--plain", "--a11y"].includes(token));
19
+ return {
20
+ plain,
21
+ command: tokens[0],
22
+ args: tokens.slice(1),
23
+ };
24
+ }
25
+
26
+ const parsed = parseCliTokens(process.argv.slice(2));
27
+ fmt.configure({ plain: parsed.plain });
28
+
29
+ const command = parsed.command;
30
+ const args = parsed.args;
11
31
 
12
32
  function initCliLocale() {
13
33
  let projectLocale = null;
@@ -27,6 +47,7 @@ function resolveRoot() {
27
47
  const context = config.resolveWorkspaceContext();
28
48
  if (!context) {
29
49
  console.error(t("cli.error.noWorkspace"));
50
+ console.error(t("cli.help.initFirst"));
30
51
  process.exit(1);
31
52
  }
32
53
  return context.workspaceRoot;
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI output formatting helpers for accessible, scannable terminal output.
5
+ * Supports a plain mode for screen readers, logs, and limited terminals.
6
+ */
7
+
8
+ const state = {
9
+ plain: false,
10
+ };
11
+
12
+ const UNICODE_SEP = "\u2500".repeat(48);
13
+ const PLAIN_SEP = "-".repeat(48);
14
+
15
+ const STATUS_TOKENS = {
16
+ pending: ["\u23F3", "PENDING"],
17
+ in_progress: ["\uD83D\uDEA7", "IN PROGRESS"],
18
+ in_review: ["\uD83D\uDC40", "IN REVIEW"],
19
+ blocked: ["\u26D4", "BLOCKED"],
20
+ completed: ["\u2705", "DONE"],
21
+ cancelled: ["\uD83D\uDDD1\uFE0F", "CANCELLED"],
22
+ };
23
+
24
+ const CHECK_TOKENS = {
25
+ pass: ["\u2705", "PASS"],
26
+ warn: ["\u26A0\uFE0F", "WARN"],
27
+ fail: ["\u274C", "FAIL"],
28
+ pending: ["\u23F3", "PENDING"],
29
+ };
30
+
31
+ function configure(options = {}) {
32
+ if (Object.prototype.hasOwnProperty.call(options, "plain")) {
33
+ state.plain = Boolean(options.plain);
34
+ }
35
+ }
36
+
37
+ function isPlain() {
38
+ return state.plain;
39
+ }
40
+
41
+ function pick(unicodeValue, plainValue) {
42
+ return state.plain ? plainValue : unicodeValue;
43
+ }
44
+
45
+ function sep() {
46
+ console.log(pick(UNICODE_SEP, PLAIN_SEP));
47
+ }
48
+
49
+ function blank() {
50
+ console.log("");
51
+ }
52
+
53
+ function header(text) {
54
+ console.log("");
55
+ console.log(pick(UNICODE_SEP, PLAIN_SEP));
56
+ console.log(` ${text}`);
57
+ console.log(pick(UNICODE_SEP, PLAIN_SEP));
58
+ }
59
+
60
+ function success(text) {
61
+ console.log(` ${pick("\u2713", "OK")} ${text}`);
62
+ }
63
+
64
+ function info(text) {
65
+ console.log(` ${text}`);
66
+ }
67
+
68
+ function step(label, value) {
69
+ if (value !== undefined) {
70
+ console.log(` ${pick("\u2192", "->")} ${label}: ${value}`);
71
+ } else {
72
+ console.log(` ${pick("\u2192", "->")} ${label}`);
73
+ }
74
+ }
75
+
76
+ function hint(text) {
77
+ console.log(` ${text}`);
78
+ }
79
+
80
+ function warn(text) {
81
+ console.log(` ${pick("\u26A0", "WARN")} ${text}`);
82
+ }
83
+
84
+ function bullet(text) {
85
+ console.log(` - ${text}`);
86
+ }
87
+
88
+ function statusToken(status) {
89
+ const pair = STATUS_TOKENS[status] || [status, String(status || "").toUpperCase()];
90
+ return pick(pair[0], `[${pair[1]}]`);
91
+ }
92
+
93
+ function checkToken(status) {
94
+ const pair = CHECK_TOKENS[status] || [status, String(status || "").toUpperCase()];
95
+ return pick(pair[0], `[${pair[1]}]`);
96
+ }
97
+
98
+ function boolToken(value) {
99
+ return value ? pick("\u2705", "[YES]") : pick("\u274C", "[NO]");
100
+ }
101
+
102
+ module.exports = {
103
+ configure,
104
+ isPlain,
105
+ pick,
106
+ sep,
107
+ blank,
108
+ header,
109
+ success,
110
+ info,
111
+ step,
112
+ hint,
113
+ warn,
114
+ bullet,
115
+ statusToken,
116
+ checkToken,
117
+ boolToken,
118
+ };
package/lib/config.js CHANGED
@@ -269,14 +269,40 @@ function getOperaVersion(control) {
269
269
  return control.meta?.opera?.version || null;
270
270
  }
271
271
 
272
+ function validateControl(control) {
273
+ if (!control || typeof control !== "object") throw new Error("project_control.json is not a valid object.");
274
+ if (!control.meta || typeof control.meta !== "object") throw new Error("project_control.json is missing required field: meta");
275
+ if (!Array.isArray(control.tasks)) throw new Error("project_control.json is missing required field: tasks");
276
+ for (let i = 0; i < control.tasks.length; i++) {
277
+ const task = control.tasks[i];
278
+ if (!task.id) throw new Error(`Task at index ${i} is missing required field: id`);
279
+ if (!task.status) throw new Error(`Task at index ${i} is missing required field: status`);
280
+ }
281
+ }
282
+
272
283
  function loadControl(contextOrRoot) {
273
284
  const filePath = controlFilePath(contextOrRoot);
274
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
285
+ let raw;
286
+ try {
287
+ raw = fs.readFileSync(filePath, "utf8");
288
+ } catch (err) {
289
+ throw new Error(`No se puede leer project_control.json.\n Ruta: ${filePath}\n Detalle: ${err.code === "ENOENT" ? "El archivo no existe. Ejecuta 'trackops init' primero." : err.message}`);
290
+ }
291
+ let control;
292
+ try {
293
+ control = JSON.parse(raw);
294
+ } catch (err) {
295
+ throw new Error(`project_control.json esta corrupto o no es JSON valido.\n Ruta: ${filePath}\n Detalle: ${err.message}`);
296
+ }
297
+ validateControl(control);
298
+ return control;
275
299
  }
276
300
 
277
- function saveControl(contextOrRoot, control) {
301
+ function saveControl(contextOrRoot, control, options) {
278
302
  control.meta = control.meta || {};
279
- control.meta.updatedAt = new Date().toISOString();
303
+ if (!(options && options.skipTimestamp)) {
304
+ control.meta.updatedAt = new Date().toISOString();
305
+ }
280
306
  const filePath = controlFilePath(contextOrRoot);
281
307
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
282
308
  fs.writeFileSync(filePath, JSON.stringify(control, null, 2) + "\n", "utf8");