trackops 1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Xavier Crespo Gríman — Baxahaun AI Venture Studio (https://baxahaun.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,358 @@
1
+ <p align="center">
2
+ <img src="docs/assets/TrackOps.webp" alt="TrackOps" width="400" />
3
+ </p>
4
+
5
+ <h1 align="center">TrackOps</h1>
6
+
7
+ <p align="center">
8
+ <strong>El motor operativo open-source para desarrolladores que construyen con IA.</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/trackops"><img src="https://img.shields.io/npm/v/trackops?color=D97706&style=flat-square" alt="npm" /></a>
13
+ <a href="LICENSE"><img src="https://img.shields.io/badge/licencia-MIT-22C55E?style=flat-square" alt="MIT" /></a>
14
+ <img src="https://img.shields.io/badge/dependencias-0-D97706?style=flat-square" alt="0 deps" />
15
+ <img src="https://img.shields.io/badge/node-%3E%3D18-333?style=flat-square" alt="Node 18+" />
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="#español">Español</a>&nbsp;&nbsp;·&nbsp;&nbsp;<a href="#english">English</a>&nbsp;&nbsp;·&nbsp;&nbsp;<a href="https://baxahaun.github.io/trackops">Web</a>
20
+ </p>
21
+
22
+ <br/>
23
+
24
+ ---
25
+
26
+ ## Español
27
+
28
+ ### El Problema: La IA es rápida. El caos también.
29
+
30
+ Escribir código con asistentes de IA (Cursor, Copilot, Claude Code, agentes autónomos) es increíblemente rápido. Pero a medida que el proyecto crece, **la IA pierde el contexto**, olvida las prioridades y el proyecto se convierte en una pesadilla de mantenimiento.
31
+
32
+ Tu agente no sabe qué tarea es prioritaria. No sabe qué está bloqueado. No sabe en qué fase estás. Y tú terminas repitiendo instrucciones en cada prompt.
33
+
34
+ ### La Solución: TrackOps
35
+
36
+ TrackOps es un **motor operativo local de código abierto** que actúa como puente entre tú (el humano) y tus agentes de IA.
37
+
38
+ **Tú** gestionas el proyecto visualmente a través de un elegante **Dashboard Web local**. TrackOps compila automáticamente ese estado en **archivos Markdown hiper-estructurados** (`task_plan.md`, `progress.md`, `findings.md`) que tu IA lee para saber exactamente qué hacer, qué reglas seguir y qué prioridades existen.
39
+
40
+ > **La IA se encarga del código. TrackOps se encarga del proyecto.**
41
+
42
+ <br/>
43
+
44
+ ### Por qué TrackOps se volverá indispensable en tu flujo
45
+
46
+ | | |
47
+ |---|---|
48
+ | **Adopción en 10 segundos** | Un solo comando: `npx trackops init`. Cero configuración, cero dependencias, cero bases de datos. |
49
+ | **Contexto determinista** | No dejes que la IA adivine. TrackOps sincroniza la verdad absoluta de tu proyecto en archivos Markdown que las IAs entienden nativamente. |
50
+ | **Dashboard local premium** | Interfaz web profesional que corre en tu terminal. Sin telemetría, sin nube, **100% privado**. |
51
+ | **Integración Git nativa** | TrackOps sabe cuándo haces commit, merge o checkout, capturando la salud del repositorio de forma automática. |
52
+ | **Portfolio multi-proyecto** | Registra todos tus proyectos y navega entre ellos desde un solo dashboard. |
53
+ | **Framework OPERA** | Metodología de desarrollo con agentes IA en 5 fases: Orquestar, Probar, Estructurar, Refinar, Automatizar. |
54
+ | **Ecosistema de Skills** | Plugins modulares que dotan a tu proyecto de capacidades automatizadas: `trackops skill install <nombre>`. |
55
+
56
+ <br/>
57
+
58
+ ### Inicio Rápido
59
+
60
+ No necesitas instalar nada globalmente. Ve a cualquier proyecto y ejecuta:
61
+
62
+ ```bash
63
+ npx trackops init # Inicializa el motor en tu proyecto
64
+ npx trackops dashboard # Levanta el centro de control web
65
+ ```
66
+
67
+ El flujo desde consola en tu día a día:
68
+
69
+ ```bash
70
+ npx trackops status # Salud del proyecto y bloqueos
71
+ npx trackops next # Siguiente tarea priorizada
72
+ npx trackops task start T-001 # Empieza a trabajar
73
+ npx trackops sync # Genera contexto Markdown para tu IA
74
+ ```
75
+
76
+ <br/>
77
+
78
+ ### Arquitectura de 3 Capas
79
+
80
+ Diseñado para escalar desde un script de fin de semana hasta infraestructura empresarial.
81
+
82
+ ```
83
+ ┌─────────────────────────────────────────────────────┐
84
+ │ Capa 3 · Ecosistema de Skills │
85
+ │ Plugins modulares para automatizar capacidades │
86
+ │ trackops skill install / list / remove / catalog │
87
+ ├─────────────────────────────────────────────────────┤
88
+ │ Capa 2 · Framework OPERA (opcional) │
89
+ │ Enrutamiento de agentes y metodología estructurada │
90
+ │ trackops opera install / configure / status │
91
+ ├─────────────────────────────────────────────────────┤
92
+ │ Capa 1 · Motor Core (siempre activo) │
93
+ │ CLI + Servidor Web Local + Generador de Markdown │
94
+ │ tareas · dashboard · registro · git hooks · sync │
95
+ └─────────────────────────────────────────────────────┘
96
+ ```
97
+
98
+ <br/>
99
+
100
+ ### Metodología OPERA
101
+
102
+ Framework opcional de 5 fases para desarrollo estructurado con IA. Cada fase tiene un **Definition of Done** verificable:
103
+
104
+ | Fase | Nombre | Foco | Entregable |
105
+ |------|--------|------|------------|
106
+ | **O** | Orquestar | Visión, datos, reglas de negocio | Schema JSON en `genesis.md` |
107
+ | **P** | Probar | Conectividad y validación | Scripts de test pasando |
108
+ | **E** | Estructurar | Construcción en 3 capas | SOPs + tools + integración |
109
+ | **R** | Refinar | Refinamiento y calidad | Outputs validados |
110
+ | **A** | Automatizar | Despliegue y triggers | Triggers + smoke test |
111
+
112
+ Las fases son totalmente configurables por proyecto vía `project_control.json`.
113
+
114
+ <br/>
115
+
116
+ ### Referencia de Comandos
117
+
118
+ <details>
119
+ <summary><strong>Motor Core</strong></summary>
120
+
121
+ | Comando | Descripción |
122
+ |---------|-------------|
123
+ | `trackops init [--with-opera] [--locale es\|en]` | Inicializar en el directorio actual |
124
+ | `trackops status` | Estado: foco, fase, tareas, bloqueadores, repo |
125
+ | `trackops next` | Próximas tareas ejecutables priorizadas |
126
+ | `trackops sync` | Regenerar task_plan.md, progress.md, findings.md |
127
+ | `trackops dashboard` | Lanzar dashboard web local |
128
+ | `trackops task <acción> <id> [nota]` | start, review, complete, block, pending, cancel, note |
129
+ | `trackops refresh-repo [--quiet]` | Actualizar runtime con estado del repo |
130
+ | `trackops register` | Registrar en el portfolio multi-proyecto |
131
+ | `trackops projects` | Listar proyectos registrados |
132
+
133
+ </details>
134
+
135
+ <details>
136
+ <summary><strong>OPERA</strong></summary>
137
+
138
+ | Comando | Descripción |
139
+ |---------|-------------|
140
+ | `trackops opera install` | Instalar metodología OPERA |
141
+ | `trackops opera status` | Estado de instalación e integridad |
142
+ | `trackops opera configure [--phases '...']` | Reconfigurar fases o idioma |
143
+ | `trackops opera upgrade` | Actualizar templates a la versión del paquete |
144
+
145
+ </details>
146
+
147
+ <details>
148
+ <summary><strong>Skills</strong></summary>
149
+
150
+ | Comando | Descripción |
151
+ |---------|-------------|
152
+ | `trackops skill install <nombre>` | Instalar skill del catálogo |
153
+ | `trackops skill list` | Listar skills instaladas |
154
+ | `trackops skill remove <nombre>` | Desinstalar skill |
155
+ | `trackops skill catalog` | Ver skills disponibles |
156
+
157
+ </details>
158
+
159
+ <br/>
160
+
161
+ ### Estructura del Proyecto
162
+
163
+ ```
164
+ mi-proyecto/
165
+ ├── project_control.json # Fuente de verdad operativa
166
+ ├── task_plan.md # Plan de tareas (auto-generado)
167
+ ├── progress.md # Diario de progreso (auto-generado)
168
+ ├── findings.md # Hallazgos (auto-generado)
169
+ ├── genesis.md # Constitución del proyecto (OPERA)
170
+ ├── .agent/hub/ # Identidad del agente + router (OPERA)
171
+ └── .agents/skills/ # Skills instaladas (OPERA)
172
+ ```
173
+
174
+ <br/>
175
+
176
+ ### Apoya el Proyecto
177
+
178
+ TrackOps es y siempre será **libre, gratuito y de código abierto** (MIT). Construimos esto para resolver un problema real de la comunidad de desarrolladores.
179
+
180
+ Si TrackOps te ha ayudado a recuperar el control de tus proyectos con IA:
181
+
182
+ 1. **Dale una estrella en GitHub** — ayuda enormemente a la visibilidad.
183
+ 2. **Comparte TrackOps** con tu equipo y en tus redes.
184
+ 3. **Contribuye** — Pull Requests, reporte de bugs y nuevas Skills son siempre bienvenidos.
185
+
186
+ <br/>
187
+
188
+ ---
189
+
190
+ ## English
191
+
192
+ ### The Problem: AI is fast. So is chaos.
193
+
194
+ Writing code with AI assistants (Cursor, Copilot, Claude Code, autonomous agents) is incredibly fast. But as the project grows, **the AI loses context**, forgets priorities, and the project becomes a maintenance nightmare.
195
+
196
+ Your agent doesn't know which task is a priority. It doesn't know what's blocked. It doesn't know what phase you're in. And you end up repeating instructions in every prompt.
197
+
198
+ ### The Solution: TrackOps
199
+
200
+ TrackOps is a **local, open-source operational engine** that acts as a bridge between you (the human) and your AI agents.
201
+
202
+ **You** manage the project visually through an elegant **local Web Dashboard**. TrackOps automatically compiles that state into **hyper-structured Markdown files** (`task_plan.md`, `progress.md`, `findings.md`) that your AI reads to know exactly what to do, what rules to follow, and what priorities exist.
203
+
204
+ > **AI handles the code. TrackOps handles the project.**
205
+
206
+ <br/>
207
+
208
+ ### Why TrackOps will become essential in your workflow
209
+
210
+ | | |
211
+ |---|---|
212
+ | **10-second adoption** | One command: `npx trackops init`. Zero config, zero dependencies, zero databases. |
213
+ | **Deterministic context** | Don't let AI guess. TrackOps syncs the absolute truth of your project into Markdown files that AIs understand natively. |
214
+ | **Premium local dashboard** | Professional web interface running in your terminal. No telemetry, no cloud, **100% private**. |
215
+ | **Native Git integration** | TrackOps knows when you commit, merge, or checkout, capturing repository health automatically. |
216
+ | **Multi-project portfolio** | Register all your projects and navigate between them from a single dashboard. |
217
+ | **OPERA Framework** | AI development methodology in 5 phases: Orchestrate, Prove, Establish, Refine, Automate. |
218
+ | **Skills ecosystem** | Modular plugins that give your project automated capabilities: `trackops skill install <name>`. |
219
+
220
+ <br/>
221
+
222
+ ### Quick Start
223
+
224
+ No global install needed. Go to any project and run:
225
+
226
+ ```bash
227
+ npx trackops init # Initialize the engine in your project
228
+ npx trackops dashboard # Launch the web control center
229
+ ```
230
+
231
+ Your daily workflow from the console:
232
+
233
+ ```bash
234
+ npx trackops status # Project health and blockers
235
+ npx trackops next # Next prioritized task
236
+ npx trackops task start T-001 # Start working
237
+ npx trackops sync # Generate Markdown context for your AI
238
+ ```
239
+
240
+ <br/>
241
+
242
+ ### 3-Layer Architecture
243
+
244
+ Designed to scale from a weekend script to enterprise infrastructure.
245
+
246
+ ```
247
+ ┌─────────────────────────────────────────────────────┐
248
+ │ Layer 3 · Skills Ecosystem │
249
+ │ Modular plugins for automated capabilities │
250
+ │ trackops skill install / list / remove / catalog │
251
+ ├─────────────────────────────────────────────────────┤
252
+ │ Layer 2 · OPERA Framework (optional) │
253
+ │ Agent routing and structured methodology │
254
+ │ trackops opera install / configure / status │
255
+ ├─────────────────────────────────────────────────────┤
256
+ │ Layer 1 · Core Engine (always active) │
257
+ │ CLI + Local Web Server + Markdown Generator │
258
+ │ tasks · dashboard · registry · git hooks · sync │
259
+ └─────────────────────────────────────────────────────┘
260
+ ```
261
+
262
+ <br/>
263
+
264
+ ### OPERA Methodology
265
+
266
+ Optional 5-phase framework for structured AI-assisted development. Each phase has a verifiable **Definition of Done**:
267
+
268
+ | Phase | Name | Focus | Deliverable |
269
+ |-------|------|-------|-------------|
270
+ | **O** | Orchestrate | Vision, data, business rules | JSON schema in `genesis.md` |
271
+ | **P** | Prove | Connectivity and validation | Passing test scripts |
272
+ | **E** | Establish | 3-layer build | SOPs + tools + integration |
273
+ | **R** | Refine | Refinement and quality | Validated outputs |
274
+ | **A** | Automate | Deployment and triggers | Triggers + smoke test |
275
+
276
+ Phases are fully configurable per project via `project_control.json`.
277
+
278
+ <br/>
279
+
280
+ ### Command Reference
281
+
282
+ <details>
283
+ <summary><strong>Core Engine</strong></summary>
284
+
285
+ | Command | Description |
286
+ |---------|-------------|
287
+ | `trackops init [--with-opera] [--locale es\|en]` | Initialize in current directory |
288
+ | `trackops status` | State: focus, phase, tasks, blockers, repo |
289
+ | `trackops next` | Next prioritized executable tasks |
290
+ | `trackops sync` | Regenerate task_plan.md, progress.md, findings.md |
291
+ | `trackops dashboard` | Launch local web dashboard |
292
+ | `trackops task <action> <id> [note]` | start, review, complete, block, pending, cancel, note |
293
+ | `trackops refresh-repo [--quiet]` | Update runtime with repo state |
294
+ | `trackops register` | Register in multi-project portfolio |
295
+ | `trackops projects` | List registered projects |
296
+
297
+ </details>
298
+
299
+ <details>
300
+ <summary><strong>OPERA</strong></summary>
301
+
302
+ | Command | Description |
303
+ |---------|-------------|
304
+ | `trackops opera install` | Install OPERA methodology |
305
+ | `trackops opera status` | Installation state and integrity |
306
+ | `trackops opera configure [--phases '...']` | Reconfigure phases or locale |
307
+ | `trackops opera upgrade` | Update templates to package version |
308
+
309
+ </details>
310
+
311
+ <details>
312
+ <summary><strong>Skills</strong></summary>
313
+
314
+ | Command | Description |
315
+ |---------|-------------|
316
+ | `trackops skill install <name>` | Install skill from catalog |
317
+ | `trackops skill list` | List installed skills |
318
+ | `trackops skill remove <name>` | Uninstall skill |
319
+ | `trackops skill catalog` | Show available skills |
320
+
321
+ </details>
322
+
323
+ <br/>
324
+
325
+ ### Project Structure
326
+
327
+ ```
328
+ my-project/
329
+ ├── project_control.json # Operational source of truth
330
+ ├── task_plan.md # Task plan (auto-generated)
331
+ ├── progress.md # Progress log (auto-generated)
332
+ ├── findings.md # Findings library (auto-generated)
333
+ ├── genesis.md # Project constitution (OPERA)
334
+ ├── .agent/hub/ # Agent identity + router (OPERA)
335
+ └── .agents/skills/ # Installed skills (OPERA)
336
+ ```
337
+
338
+ <br/>
339
+
340
+ ### Support the Project
341
+
342
+ TrackOps is and will always be **free and open-source** (MIT). We built this to solve a real problem in the developer community.
343
+
344
+ If TrackOps has helped you regain control of your AI-assisted projects:
345
+
346
+ 1. **Star us on GitHub** — it helps enormously with visibility.
347
+ 2. **Share TrackOps** with your team and on social media.
348
+ 3. **Contribute** — Pull Requests, bug reports, and new Skills are always welcome.
349
+
350
+ <br/>
351
+
352
+ ---
353
+
354
+ <p align="center">
355
+ <a href="https://baxahaun.com"><strong>Xavier Crespo Gríman</strong></a> · <a href="https://baxahaun.com">Baxahaun AI Venture Studio</a>
356
+ <br/>
357
+ <a href="LICENSE">MIT License</a> · 2026
358
+ </p>
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require("path");
4
+ const config = require("../lib/config");
5
+
6
+ const command = process.argv[2];
7
+ const args = process.argv.slice(3);
8
+
9
+ function resolveRoot() {
10
+ const root = config.resolveProjectRoot();
11
+ if (!root) {
12
+ console.error("No project_control.json found in this directory or any parent.");
13
+ process.exit(1);
14
+ }
15
+ return root;
16
+ }
17
+
18
+ function run() {
19
+ try {
20
+ switch (command) {
21
+ case "init":
22
+ require("../lib/init").cmdInit(args);
23
+ break;
24
+
25
+ case "status":
26
+ require("../lib/control").cmdStatus(resolveRoot());
27
+ break;
28
+
29
+ case "next":
30
+ require("../lib/control").cmdNext(resolveRoot());
31
+ break;
32
+
33
+ case "sync":
34
+ require("../lib/control").cmdSync(resolveRoot());
35
+ break;
36
+
37
+ case "dashboard":
38
+ require("../lib/server").run();
39
+ break;
40
+
41
+ case "refresh-repo":
42
+ require("../lib/control").cmdRefreshRepo(resolveRoot(), args);
43
+ break;
44
+
45
+ case "install-hooks":
46
+ require("../lib/control").cmdInstallHooks(resolveRoot());
47
+ break;
48
+
49
+ case "task":
50
+ require("../lib/control").cmdTask(resolveRoot(), args);
51
+ break;
52
+
53
+ case "register":
54
+ require("../lib/registry").cmdRegister(config.resolveProjectRoot() || process.cwd());
55
+ break;
56
+
57
+ case "projects":
58
+ require("../lib/registry").cmdList();
59
+ break;
60
+
61
+ case "opera": {
62
+ const opera = require("../lib/opera");
63
+ const sub = args[0];
64
+ const root = config.resolveProjectRoot() || process.cwd();
65
+ if (sub === "install") opera.cmdInstall(root, args.slice(1));
66
+ else if (sub === "status") opera.cmdStatus(root);
67
+ else if (sub === "configure") opera.cmdConfigure(root, args.slice(1));
68
+ else if (sub === "upgrade") opera.cmdUpgrade(root);
69
+ else { console.log("Usage: trackops opera <install|status|configure|upgrade>"); }
70
+ break;
71
+ }
72
+
73
+ case "skill": {
74
+ const skills = require("../lib/skills");
75
+ const sub = args[0];
76
+ const root = config.resolveProjectRoot() || process.cwd();
77
+ if (sub === "install") skills.cmdInstall(root, args[1]);
78
+ else if (sub === "list") skills.cmdList(root);
79
+ else if (sub === "remove") skills.cmdRemove(root, args[1]);
80
+ else if (sub === "catalog") skills.cmdCatalog();
81
+ else { console.log("Usage: trackops skill <install|list|remove|catalog> [name]"); }
82
+ break;
83
+ }
84
+
85
+ case "help":
86
+ case "--help":
87
+ case "-h":
88
+ case undefined:
89
+ require("../lib/control").cmdHelp();
90
+ break;
91
+
92
+ default:
93
+ console.error(`Unknown command: ${command}`);
94
+ console.error("Run 'trackops help' for usage.");
95
+ process.exit(1);
96
+ }
97
+ } catch (error) {
98
+ console.error(error.message);
99
+ process.exit(1);
100
+ }
101
+ }
102
+
103
+ run();
package/lib/config.js ADDED
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const DEFAULT_PHASES = [
7
+ { id: "O", label: "Orquestar", index: 1 },
8
+ { id: "P", label: "Probar", index: 2 },
9
+ { id: "E", label: "Estructurar", index: 3 },
10
+ { id: "R", label: "Refinar", index: 4 },
11
+ { id: "A", label: "Automatizar", index: 5 },
12
+ ];
13
+
14
+ const DEFAULT_LOCALE = "es";
15
+
16
+ function resolveProjectRoot(startDir) {
17
+ let dir = path.resolve(startDir || process.cwd());
18
+ const root = path.parse(dir).root;
19
+ while (dir !== root) {
20
+ if (fs.existsSync(path.join(dir, "project_control.json"))) {
21
+ return dir;
22
+ }
23
+ dir = path.dirname(dir);
24
+ }
25
+ return null;
26
+ }
27
+
28
+ function controlFilePath(root) {
29
+ return path.join(root, "project_control.json");
30
+ }
31
+
32
+ function runtimeFilePath(root) {
33
+ return path.join(root, ".tmp", "project-control-runtime.json");
34
+ }
35
+
36
+ function docFilePaths(root) {
37
+ return {
38
+ taskPlan: path.join(root, "task_plan.md"),
39
+ progress: path.join(root, "progress.md"),
40
+ findings: path.join(root, "findings.md"),
41
+ };
42
+ }
43
+
44
+ function getPhases(control) {
45
+ if (
46
+ Array.isArray(control.meta?.phases) &&
47
+ control.meta.phases.length > 0
48
+ ) {
49
+ return control.meta.phases;
50
+ }
51
+ return DEFAULT_PHASES;
52
+ }
53
+
54
+ function getLocale(control) {
55
+ return control.meta?.locale || DEFAULT_LOCALE;
56
+ }
57
+
58
+ function isOperaInstalled(control) {
59
+ return control.meta?.opera?.installed === true;
60
+ }
61
+
62
+ function getOperaVersion(control) {
63
+ return control.meta?.opera?.version || null;
64
+ }
65
+
66
+ // Backwards-compat aliases
67
+ function isEtapaInstalled(control) { return isOperaInstalled(control); }
68
+ function getEtapaVersion(control) { return getOperaVersion(control); }
69
+
70
+ function loadControl(root) {
71
+ const filePath = controlFilePath(root);
72
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
73
+ }
74
+
75
+ function saveControl(root, control) {
76
+ control.meta = control.meta || {};
77
+ control.meta.updatedAt = new Date().toISOString();
78
+ const filePath = controlFilePath(root);
79
+ fs.writeFileSync(filePath, JSON.stringify(control, null, 2) + "\n", "utf8");
80
+ }
81
+
82
+ module.exports = {
83
+ DEFAULT_PHASES,
84
+ DEFAULT_LOCALE,
85
+ resolveProjectRoot,
86
+ controlFilePath,
87
+ runtimeFilePath,
88
+ docFilePaths,
89
+ getPhases,
90
+ getLocale,
91
+ isOperaInstalled,
92
+ getOperaVersion,
93
+ isEtapaInstalled,
94
+ getEtapaVersion,
95
+ loadControl,
96
+ saveControl,
97
+ };