trackops 2.0.4 → 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.
Files changed (92) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +660 -575
  3. package/bin/trackops.js +127 -106
  4. package/lib/cli-format.js +118 -0
  5. package/lib/config.js +352 -326
  6. package/lib/control.js +408 -246
  7. package/lib/env.js +234 -222
  8. package/lib/i18n.js +5 -4
  9. package/lib/init.js +390 -282
  10. package/lib/locale.js +41 -41
  11. package/lib/opera-bootstrap.js +1066 -880
  12. package/lib/opera.js +615 -444
  13. package/lib/preferences.js +74 -74
  14. package/lib/registry.js +214 -214
  15. package/lib/release.js +56 -56
  16. package/lib/runtime-state.js +144 -144
  17. package/lib/skills.js +114 -89
  18. package/lib/workspace.js +259 -248
  19. package/locales/en.json +311 -167
  20. package/locales/es.json +314 -170
  21. package/package.json +61 -58
  22. package/scripts/postinstall-locale.js +21 -21
  23. package/scripts/skills-marketplace-smoke.js +124 -124
  24. package/scripts/smoke-tests.js +563 -517
  25. package/scripts/sync-skill-version.js +21 -21
  26. package/scripts/validate-skill.js +103 -103
  27. package/skills/trackops/SKILL.md +126 -122
  28. package/skills/trackops/agents/openai.yaml +7 -7
  29. package/skills/trackops/locales/en/SKILL.md +126 -122
  30. package/skills/trackops/locales/en/references/activation.md +94 -90
  31. package/skills/trackops/locales/en/references/troubleshooting.md +73 -67
  32. package/skills/trackops/locales/en/references/workflow.md +55 -32
  33. package/skills/trackops/references/activation.md +94 -90
  34. package/skills/trackops/references/troubleshooting.md +73 -67
  35. package/skills/trackops/references/workflow.md +55 -32
  36. package/skills/trackops/skill.json +29 -29
  37. package/templates/hooks/post-checkout +2 -2
  38. package/templates/hooks/post-commit +2 -2
  39. package/templates/hooks/post-merge +2 -2
  40. package/templates/opera/agent.md +28 -27
  41. package/templates/opera/architecture/dependency-graph.md +24 -24
  42. package/templates/opera/architecture/runtime-automation.md +24 -24
  43. package/templates/opera/architecture/runtime-operations.md +34 -34
  44. package/templates/opera/en/agent.md +22 -21
  45. package/templates/opera/en/architecture/dependency-graph.md +24 -24
  46. package/templates/opera/en/architecture/runtime-automation.md +24 -24
  47. package/templates/opera/en/architecture/runtime-operations.md +34 -34
  48. package/templates/opera/en/reviews/delivery-audit.md +18 -18
  49. package/templates/opera/en/reviews/integration-audit.md +18 -18
  50. package/templates/opera/en/router.md +24 -19
  51. package/templates/opera/references/autonomy-and-recovery.md +117 -117
  52. package/templates/opera/references/opera-cycle.md +193 -193
  53. package/templates/opera/registry.md +28 -28
  54. package/templates/opera/reviews/delivery-audit.md +18 -18
  55. package/templates/opera/reviews/integration-audit.md +18 -18
  56. package/templates/opera/router.md +54 -49
  57. package/templates/skills/changelog-updater/SKILL.md +69 -69
  58. package/templates/skills/commiter/SKILL.md +99 -99
  59. package/templates/skills/opera-contract-auditor/SKILL.md +38 -38
  60. package/templates/skills/opera-contract-auditor/locales/en/SKILL.md +38 -38
  61. package/templates/skills/opera-policy-guard/SKILL.md +26 -26
  62. package/templates/skills/opera-policy-guard/locales/en/SKILL.md +26 -26
  63. package/templates/skills/opera-skill/SKILL.md +279 -0
  64. package/templates/skills/opera-skill/locales/en/SKILL.md +279 -0
  65. package/templates/skills/opera-skill/locales/en/references/phase-dod.md +138 -0
  66. package/templates/skills/opera-skill/references/phase-dod.md +138 -0
  67. package/templates/skills/project-starter-skill/SKILL.md +150 -131
  68. package/templates/skills/project-starter-skill/locales/en/SKILL.md +143 -105
  69. package/templates/skills/project-starter-skill/references/opera-cycle.md +195 -193
  70. package/ui/css/base.css +284 -284
  71. package/ui/css/charts.css +425 -425
  72. package/ui/css/components.css +1107 -1107
  73. package/ui/css/onboarding.css +133 -133
  74. package/ui/css/terminal.css +125 -125
  75. package/ui/css/timeline.css +58 -58
  76. package/ui/css/tokens.css +284 -284
  77. package/ui/favicon.svg +5 -5
  78. package/ui/index.html +99 -99
  79. package/ui/js/charts.js +526 -526
  80. package/ui/js/console-logger.js +172 -172
  81. package/ui/js/filters.js +247 -247
  82. package/ui/js/icons.js +129 -129
  83. package/ui/js/keyboard.js +229 -229
  84. package/ui/js/router.js +142 -142
  85. package/ui/js/theme.js +100 -100
  86. package/ui/js/time-tracker.js +248 -248
  87. package/ui/js/views/dashboard.js +870 -870
  88. package/ui/js/views/flash.js +47 -47
  89. package/ui/js/views/projects.js +745 -745
  90. package/ui/js/views/scrum.js +476 -476
  91. package/ui/js/views/settings.js +331 -331
  92. package/ui/js/views/timeline.js +265 -265
package/lib/config.js CHANGED
@@ -1,326 +1,352 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require("fs");
4
- const path = require("path");
5
- const { normalizeLocale } = require("./locale");
6
-
7
- const DEFAULT_PHASE_IDS = ["O", "P", "E", "R", "A"];
8
- const DEFAULT_PHASE_LABELS = {
9
- es: {
10
- O: "Orquestar",
11
- P: "Probar",
12
- E: "Estructurar",
13
- R: "Refinar",
14
- A: "Automatizar",
15
- },
16
- en: {
17
- O: "Orchestrate",
18
- P: "Prove",
19
- E: "Establish",
20
- R: "Refine",
21
- A: "Automate",
22
- },
23
- };
24
-
25
- const DEFAULT_LOCALE = "es";
26
- const WORKSPACE_MANIFEST = ".trackops-workspace.json";
27
- const DEFAULT_APP_DIR = "app";
28
- const DEFAULT_OPS_DIR = "ops";
29
- const DEFAULT_DEV_BRANCH = "develop";
30
- const DEFAULT_PUBLISH_BRANCH = "master";
31
-
32
- function buildDefaultPhases(locale) {
33
- const normalized = normalizeLocale(locale) || DEFAULT_LOCALE;
34
- const labels = DEFAULT_PHASE_LABELS[normalized] || DEFAULT_PHASE_LABELS[DEFAULT_LOCALE];
35
- return DEFAULT_PHASE_IDS.map((id, index) => ({
36
- id,
37
- label: labels[id],
38
- index: index + 1,
39
- }));
40
- }
41
-
42
- const DEFAULT_PHASES = buildDefaultPhases(DEFAULT_LOCALE);
43
-
44
- function fileExists(filePath) {
45
- try {
46
- return fs.existsSync(filePath);
47
- } catch (_error) {
48
- return false;
49
- }
50
- }
51
-
52
- function readJson(filePath, fallback = null) {
53
- if (!fileExists(filePath)) return fallback;
54
- try {
55
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
56
- } catch (_error) {
57
- return fallback;
58
- }
59
- }
60
-
61
- function createSplitContext(workspaceRoot, manifest = {}) {
62
- const appDir = manifest.appDir || DEFAULT_APP_DIR;
63
- const opsDir = manifest.opsDir || DEFAULT_OPS_DIR;
64
- const workspace = path.resolve(workspaceRoot);
65
- const appRoot = path.join(workspace, appDir);
66
- const opsRoot = path.join(workspace, opsDir);
67
- const env = manifest.env || {};
68
-
69
- return {
70
- layout: "split",
71
- workspaceRoot: workspace,
72
- root: workspace,
73
- projectRoot: workspace,
74
- appRoot,
75
- opsRoot,
76
- manifestFile: path.join(workspace, WORKSPACE_MANIFEST),
77
- packageFile: path.join(appRoot, "package.json"),
78
- controlFile: path.join(opsRoot, "project_control.json"),
79
- runtimeFile: path.join(opsRoot, ".tmp", "project-control-runtime.json"),
80
- docs: {
81
- taskPlan: path.join(opsRoot, "task_plan.md"),
82
- progress: path.join(opsRoot, "progress.md"),
83
- findings: path.join(opsRoot, "findings.md"),
84
- },
85
- paths: {
86
- taskPlan: path.join(opsRoot, "task_plan.md"),
87
- progress: path.join(opsRoot, "progress.md"),
88
- findings: path.join(opsRoot, "findings.md"),
89
- architectureDir: path.join(opsRoot, "architecture"),
90
- hooksDir: path.join(opsRoot, ".githooks"),
91
- tmpDir: path.join(opsRoot, ".tmp"),
92
- bootstrapDir: path.join(opsRoot, "bootstrap"),
93
- contractDir: path.join(opsRoot, "contract"),
94
- contractFile: path.join(opsRoot, "contract", "operating-contract.json"),
95
- policyDir: path.join(opsRoot, "policy"),
96
- autonomyPolicyFile: path.join(opsRoot, "policy", "autonomy.json"),
97
- reviewsDir: path.join(opsRoot, "reviews"),
98
- skillsDir: path.join(opsRoot, ".agents", "skills"),
99
- registryPath: path.join(opsRoot, ".agents", "skills", "_registry.md"),
100
- agentHubDir: path.join(opsRoot, ".agent", "hub"),
101
- genesisFile: path.join(opsRoot, "genesis.md"),
102
- },
103
- env: {
104
- rootFile: path.join(workspace, env.rootFile || ".env"),
105
- exampleFile: path.join(workspace, env.exampleFile || ".env.example"),
106
- appBridgeFile: path.join(workspace, env.appBridgeFile || path.join(appDir, ".env")),
107
- appExampleBridgeFile: path.join(appRoot, ".env.example"),
108
- bridgeMode: env.bridgeMode || "symlink-or-copy",
109
- },
110
- branches: {
111
- development: manifest.branches?.development || DEFAULT_DEV_BRANCH,
112
- publish: manifest.branches?.publish || DEFAULT_PUBLISH_BRANCH,
113
- },
114
- publish: {
115
- mode: manifest.publish?.mode || "subtree-flatten",
116
- sourceDir: manifest.publish?.sourceDir || appDir,
117
- includeRootFiles: Array.isArray(manifest.publish?.includeRootFiles)
118
- ? manifest.publish.includeRootFiles
119
- : [".env.example"],
120
- requireCleanWorktree: manifest.publish?.requireCleanWorktree !== false,
121
- },
122
- };
123
- }
124
-
125
- function createLegacyContext(rootDir) {
126
- const root = path.resolve(rootDir);
127
- return {
128
- layout: "legacy",
129
- workspaceRoot: root,
130
- root,
131
- projectRoot: root,
132
- appRoot: root,
133
- opsRoot: root,
134
- manifestFile: null,
135
- packageFile: path.join(root, "package.json"),
136
- controlFile: path.join(root, "project_control.json"),
137
- runtimeFile: path.join(root, ".tmp", "project-control-runtime.json"),
138
- docs: {
139
- taskPlan: path.join(root, "task_plan.md"),
140
- progress: path.join(root, "progress.md"),
141
- findings: path.join(root, "findings.md"),
142
- },
143
- paths: {
144
- taskPlan: path.join(root, "task_plan.md"),
145
- progress: path.join(root, "progress.md"),
146
- findings: path.join(root, "findings.md"),
147
- architectureDir: path.join(root, "architecture"),
148
- hooksDir: path.join(root, ".githooks"),
149
- tmpDir: path.join(root, ".tmp"),
150
- bootstrapDir: path.join(root, "bootstrap"),
151
- contractDir: path.join(root, "contract"),
152
- contractFile: path.join(root, "contract", "operating-contract.json"),
153
- policyDir: path.join(root, "policy"),
154
- autonomyPolicyFile: path.join(root, "policy", "autonomy.json"),
155
- reviewsDir: path.join(root, "reviews"),
156
- skillsDir: path.join(root, ".agents", "skills"),
157
- registryPath: path.join(root, ".agents", "skills", "_registry.md"),
158
- agentHubDir: path.join(root, ".agent", "hub"),
159
- genesisFile: path.join(root, "genesis.md"),
160
- },
161
- env: {
162
- rootFile: path.join(root, ".env"),
163
- exampleFile: path.join(root, ".env.example"),
164
- appBridgeFile: path.join(root, ".env"),
165
- appExampleBridgeFile: path.join(root, ".env.example"),
166
- bridgeMode: "none",
167
- },
168
- branches: {
169
- development: DEFAULT_DEV_BRANCH,
170
- publish: DEFAULT_PUBLISH_BRANCH,
171
- },
172
- publish: {
173
- mode: "legacy",
174
- sourceDir: ".",
175
- includeRootFiles: [".env.example"],
176
- requireCleanWorktree: true,
177
- },
178
- };
179
- }
180
-
181
- function resolveWorkspaceContext(startDir) {
182
- let dir = path.resolve(startDir || process.cwd());
183
- const root = path.parse(dir).root;
184
- let legacyCandidate = null;
185
-
186
- while (true) {
187
- const manifestFile = path.join(dir, WORKSPACE_MANIFEST);
188
- if (fileExists(manifestFile)) {
189
- return createSplitContext(dir, readJson(manifestFile, {}) || {});
190
- }
191
-
192
- if (!legacyCandidate && fileExists(path.join(dir, "project_control.json"))) {
193
- legacyCandidate = dir;
194
- }
195
-
196
- if (dir === root) break;
197
- dir = path.dirname(dir);
198
- }
199
-
200
- if (legacyCandidate) {
201
- return createLegacyContext(legacyCandidate);
202
- }
203
-
204
- return null;
205
- }
206
-
207
- function ensureContext(contextOrRoot) {
208
- if (!contextOrRoot) return resolveWorkspaceContext(process.cwd());
209
- if (typeof contextOrRoot === "object" && contextOrRoot.workspaceRoot) return contextOrRoot;
210
- const resolved = resolveWorkspaceContext(contextOrRoot);
211
- if (resolved) return resolved;
212
- return createLegacyContext(contextOrRoot);
213
- }
214
-
215
- function resolveProjectRoot(startDir) {
216
- const context = resolveWorkspaceContext(startDir);
217
- return context ? context.workspaceRoot : null;
218
- }
219
-
220
- function controlFilePath(contextOrRoot) {
221
- return ensureContext(contextOrRoot).controlFile;
222
- }
223
-
224
- function runtimeFilePath(contextOrRoot) {
225
- return ensureContext(contextOrRoot).runtimeFile;
226
- }
227
-
228
- function docFilePaths(contextOrRoot) {
229
- return ensureContext(contextOrRoot).docs;
230
- }
231
-
232
- function envFilePaths(contextOrRoot) {
233
- return ensureContext(contextOrRoot).env;
234
- }
235
-
236
- function packageFilePath(contextOrRoot) {
237
- return ensureContext(contextOrRoot).packageFile;
238
- }
239
-
240
- function workspaceManifestPath(contextOrRoot) {
241
- return ensureContext(contextOrRoot).manifestFile;
242
- }
243
-
244
- function isDefaultPhaseShape(phases) {
245
- if (!Array.isArray(phases) || phases.length !== DEFAULT_PHASE_IDS.length) return false;
246
- return phases.every((phase, index) => phase?.id === DEFAULT_PHASE_IDS[index]);
247
- }
248
-
249
- function getPhases(control, localeOverride) {
250
- const locale = normalizeLocale(localeOverride || control.meta?.locale) || DEFAULT_LOCALE;
251
- if (Array.isArray(control.meta?.phases) && control.meta.phases.length > 0) {
252
- if (isDefaultPhaseShape(control.meta.phases)) {
253
- return buildDefaultPhases(locale);
254
- }
255
- return control.meta.phases;
256
- }
257
- return buildDefaultPhases(locale);
258
- }
259
-
260
- function getLocale(control) {
261
- return normalizeLocale(control.meta?.locale) || DEFAULT_LOCALE;
262
- }
263
-
264
- function isOperaInstalled(control) {
265
- return control.meta?.opera?.installed === true;
266
- }
267
-
268
- function getOperaVersion(control) {
269
- return control.meta?.opera?.version || null;
270
- }
271
-
272
- function loadControl(contextOrRoot) {
273
- const filePath = controlFilePath(contextOrRoot);
274
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
275
- }
276
-
277
- function saveControl(contextOrRoot, control) {
278
- control.meta = control.meta || {};
279
- control.meta.updatedAt = new Date().toISOString();
280
- const filePath = controlFilePath(contextOrRoot);
281
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
282
- fs.writeFileSync(filePath, JSON.stringify(control, null, 2) + "\n", "utf8");
283
- }
284
-
285
- function loadWorkspaceManifest(contextOrRoot) {
286
- const context = ensureContext(contextOrRoot);
287
- if (!context.manifestFile || !fileExists(context.manifestFile)) return null;
288
- return readJson(context.manifestFile, null);
289
- }
290
-
291
- function saveWorkspaceManifest(contextOrRoot, manifest) {
292
- const context = ensureContext(contextOrRoot);
293
- const manifestFile = context.manifestFile || path.join(context.workspaceRoot, WORKSPACE_MANIFEST);
294
- fs.writeFileSync(manifestFile, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
295
- }
296
-
297
- module.exports = {
298
- DEFAULT_PHASES,
299
- DEFAULT_PHASE_IDS,
300
- DEFAULT_LOCALE,
301
- DEFAULT_APP_DIR,
302
- DEFAULT_OPS_DIR,
303
- DEFAULT_DEV_BRANCH,
304
- DEFAULT_PUBLISH_BRANCH,
305
- WORKSPACE_MANIFEST,
306
- buildDefaultPhases,
307
- createSplitContext,
308
- createLegacyContext,
309
- resolveWorkspaceContext,
310
- resolveProjectRoot,
311
- ensureContext,
312
- controlFilePath,
313
- runtimeFilePath,
314
- docFilePaths,
315
- envFilePaths,
316
- packageFilePath,
317
- workspaceManifestPath,
318
- loadWorkspaceManifest,
319
- saveWorkspaceManifest,
320
- getPhases,
321
- getLocale,
322
- isOperaInstalled,
323
- getOperaVersion,
324
- loadControl,
325
- saveControl,
326
- };
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { normalizeLocale } = require("./locale");
6
+
7
+ const DEFAULT_PHASE_IDS = ["O", "P", "E", "R", "A"];
8
+ const DEFAULT_PHASE_LABELS = {
9
+ es: {
10
+ O: "Orquestar",
11
+ P: "Probar",
12
+ E: "Estructurar",
13
+ R: "Refinar",
14
+ A: "Automatizar",
15
+ },
16
+ en: {
17
+ O: "Orchestrate",
18
+ P: "Prove",
19
+ E: "Establish",
20
+ R: "Refine",
21
+ A: "Automate",
22
+ },
23
+ };
24
+
25
+ const DEFAULT_LOCALE = "es";
26
+ const WORKSPACE_MANIFEST = ".trackops-workspace.json";
27
+ const DEFAULT_APP_DIR = "app";
28
+ const DEFAULT_OPS_DIR = "ops";
29
+ const DEFAULT_DEV_BRANCH = "develop";
30
+ const DEFAULT_PUBLISH_BRANCH = "master";
31
+
32
+ function buildDefaultPhases(locale) {
33
+ const normalized = normalizeLocale(locale) || DEFAULT_LOCALE;
34
+ const labels = DEFAULT_PHASE_LABELS[normalized] || DEFAULT_PHASE_LABELS[DEFAULT_LOCALE];
35
+ return DEFAULT_PHASE_IDS.map((id, index) => ({
36
+ id,
37
+ label: labels[id],
38
+ index: index + 1,
39
+ }));
40
+ }
41
+
42
+ const DEFAULT_PHASES = buildDefaultPhases(DEFAULT_LOCALE);
43
+
44
+ function fileExists(filePath) {
45
+ try {
46
+ return fs.existsSync(filePath);
47
+ } catch (_error) {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ function readJson(filePath, fallback = null) {
53
+ if (!fileExists(filePath)) return fallback;
54
+ try {
55
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
56
+ } catch (_error) {
57
+ return fallback;
58
+ }
59
+ }
60
+
61
+ function createSplitContext(workspaceRoot, manifest = {}) {
62
+ const appDir = manifest.appDir || DEFAULT_APP_DIR;
63
+ const opsDir = manifest.opsDir || DEFAULT_OPS_DIR;
64
+ const workspace = path.resolve(workspaceRoot);
65
+ const appRoot = path.join(workspace, appDir);
66
+ const opsRoot = path.join(workspace, opsDir);
67
+ const env = manifest.env || {};
68
+
69
+ return {
70
+ layout: "split",
71
+ workspaceRoot: workspace,
72
+ root: workspace,
73
+ projectRoot: workspace,
74
+ appRoot,
75
+ opsRoot,
76
+ manifestFile: path.join(workspace, WORKSPACE_MANIFEST),
77
+ packageFile: path.join(appRoot, "package.json"),
78
+ controlFile: path.join(opsRoot, "project_control.json"),
79
+ runtimeFile: path.join(opsRoot, ".tmp", "project-control-runtime.json"),
80
+ docs: {
81
+ taskPlan: path.join(opsRoot, "task_plan.md"),
82
+ progress: path.join(opsRoot, "progress.md"),
83
+ findings: path.join(opsRoot, "findings.md"),
84
+ },
85
+ paths: {
86
+ taskPlan: path.join(opsRoot, "task_plan.md"),
87
+ progress: path.join(opsRoot, "progress.md"),
88
+ findings: path.join(opsRoot, "findings.md"),
89
+ architectureDir: path.join(opsRoot, "architecture"),
90
+ hooksDir: path.join(opsRoot, ".githooks"),
91
+ tmpDir: path.join(opsRoot, ".tmp"),
92
+ bootstrapDir: path.join(opsRoot, "bootstrap"),
93
+ contractDir: path.join(opsRoot, "contract"),
94
+ contractFile: path.join(opsRoot, "contract", "operating-contract.json"),
95
+ policyDir: path.join(opsRoot, "policy"),
96
+ autonomyPolicyFile: path.join(opsRoot, "policy", "autonomy.json"),
97
+ reviewsDir: path.join(opsRoot, "reviews"),
98
+ skillsDir: path.join(opsRoot, ".agents", "skills"),
99
+ registryPath: path.join(opsRoot, ".agents", "skills", "_registry.md"),
100
+ agentHubDir: path.join(opsRoot, ".agent", "hub"),
101
+ genesisFile: path.join(opsRoot, "genesis.md"),
102
+ },
103
+ env: {
104
+ rootFile: path.join(workspace, env.rootFile || ".env"),
105
+ exampleFile: path.join(workspace, env.exampleFile || ".env.example"),
106
+ appBridgeFile: path.join(workspace, env.appBridgeFile || path.join(appDir, ".env")),
107
+ appExampleBridgeFile: path.join(appRoot, ".env.example"),
108
+ bridgeMode: env.bridgeMode || "symlink-or-copy",
109
+ },
110
+ branches: {
111
+ development: manifest.branches?.development || DEFAULT_DEV_BRANCH,
112
+ publish: manifest.branches?.publish || DEFAULT_PUBLISH_BRANCH,
113
+ },
114
+ publish: {
115
+ mode: manifest.publish?.mode || "subtree-flatten",
116
+ sourceDir: manifest.publish?.sourceDir || appDir,
117
+ includeRootFiles: Array.isArray(manifest.publish?.includeRootFiles)
118
+ ? manifest.publish.includeRootFiles
119
+ : [".env.example"],
120
+ requireCleanWorktree: manifest.publish?.requireCleanWorktree !== false,
121
+ },
122
+ };
123
+ }
124
+
125
+ function createLegacyContext(rootDir) {
126
+ const root = path.resolve(rootDir);
127
+ return {
128
+ layout: "legacy",
129
+ workspaceRoot: root,
130
+ root,
131
+ projectRoot: root,
132
+ appRoot: root,
133
+ opsRoot: root,
134
+ manifestFile: null,
135
+ packageFile: path.join(root, "package.json"),
136
+ controlFile: path.join(root, "project_control.json"),
137
+ runtimeFile: path.join(root, ".tmp", "project-control-runtime.json"),
138
+ docs: {
139
+ taskPlan: path.join(root, "task_plan.md"),
140
+ progress: path.join(root, "progress.md"),
141
+ findings: path.join(root, "findings.md"),
142
+ },
143
+ paths: {
144
+ taskPlan: path.join(root, "task_plan.md"),
145
+ progress: path.join(root, "progress.md"),
146
+ findings: path.join(root, "findings.md"),
147
+ architectureDir: path.join(root, "architecture"),
148
+ hooksDir: path.join(root, ".githooks"),
149
+ tmpDir: path.join(root, ".tmp"),
150
+ bootstrapDir: path.join(root, "bootstrap"),
151
+ contractDir: path.join(root, "contract"),
152
+ contractFile: path.join(root, "contract", "operating-contract.json"),
153
+ policyDir: path.join(root, "policy"),
154
+ autonomyPolicyFile: path.join(root, "policy", "autonomy.json"),
155
+ reviewsDir: path.join(root, "reviews"),
156
+ skillsDir: path.join(root, ".agents", "skills"),
157
+ registryPath: path.join(root, ".agents", "skills", "_registry.md"),
158
+ agentHubDir: path.join(root, ".agent", "hub"),
159
+ genesisFile: path.join(root, "genesis.md"),
160
+ },
161
+ env: {
162
+ rootFile: path.join(root, ".env"),
163
+ exampleFile: path.join(root, ".env.example"),
164
+ appBridgeFile: path.join(root, ".env"),
165
+ appExampleBridgeFile: path.join(root, ".env.example"),
166
+ bridgeMode: "none",
167
+ },
168
+ branches: {
169
+ development: DEFAULT_DEV_BRANCH,
170
+ publish: DEFAULT_PUBLISH_BRANCH,
171
+ },
172
+ publish: {
173
+ mode: "legacy",
174
+ sourceDir: ".",
175
+ includeRootFiles: [".env.example"],
176
+ requireCleanWorktree: true,
177
+ },
178
+ };
179
+ }
180
+
181
+ function resolveWorkspaceContext(startDir) {
182
+ let dir = path.resolve(startDir || process.cwd());
183
+ const root = path.parse(dir).root;
184
+ let legacyCandidate = null;
185
+
186
+ while (true) {
187
+ const manifestFile = path.join(dir, WORKSPACE_MANIFEST);
188
+ if (fileExists(manifestFile)) {
189
+ return createSplitContext(dir, readJson(manifestFile, {}) || {});
190
+ }
191
+
192
+ if (!legacyCandidate && fileExists(path.join(dir, "project_control.json"))) {
193
+ legacyCandidate = dir;
194
+ }
195
+
196
+ if (dir === root) break;
197
+ dir = path.dirname(dir);
198
+ }
199
+
200
+ if (legacyCandidate) {
201
+ return createLegacyContext(legacyCandidate);
202
+ }
203
+
204
+ return null;
205
+ }
206
+
207
+ function ensureContext(contextOrRoot) {
208
+ if (!contextOrRoot) return resolveWorkspaceContext(process.cwd());
209
+ if (typeof contextOrRoot === "object" && contextOrRoot.workspaceRoot) return contextOrRoot;
210
+ const resolved = resolveWorkspaceContext(contextOrRoot);
211
+ if (resolved) return resolved;
212
+ return createLegacyContext(contextOrRoot);
213
+ }
214
+
215
+ function resolveProjectRoot(startDir) {
216
+ const context = resolveWorkspaceContext(startDir);
217
+ return context ? context.workspaceRoot : null;
218
+ }
219
+
220
+ function controlFilePath(contextOrRoot) {
221
+ return ensureContext(contextOrRoot).controlFile;
222
+ }
223
+
224
+ function runtimeFilePath(contextOrRoot) {
225
+ return ensureContext(contextOrRoot).runtimeFile;
226
+ }
227
+
228
+ function docFilePaths(contextOrRoot) {
229
+ return ensureContext(contextOrRoot).docs;
230
+ }
231
+
232
+ function envFilePaths(contextOrRoot) {
233
+ return ensureContext(contextOrRoot).env;
234
+ }
235
+
236
+ function packageFilePath(contextOrRoot) {
237
+ return ensureContext(contextOrRoot).packageFile;
238
+ }
239
+
240
+ function workspaceManifestPath(contextOrRoot) {
241
+ return ensureContext(contextOrRoot).manifestFile;
242
+ }
243
+
244
+ function isDefaultPhaseShape(phases) {
245
+ if (!Array.isArray(phases) || phases.length !== DEFAULT_PHASE_IDS.length) return false;
246
+ return phases.every((phase, index) => phase?.id === DEFAULT_PHASE_IDS[index]);
247
+ }
248
+
249
+ function getPhases(control, localeOverride) {
250
+ const locale = normalizeLocale(localeOverride || control.meta?.locale) || DEFAULT_LOCALE;
251
+ if (Array.isArray(control.meta?.phases) && control.meta.phases.length > 0) {
252
+ if (isDefaultPhaseShape(control.meta.phases)) {
253
+ return buildDefaultPhases(locale);
254
+ }
255
+ return control.meta.phases;
256
+ }
257
+ return buildDefaultPhases(locale);
258
+ }
259
+
260
+ function getLocale(control) {
261
+ return normalizeLocale(control.meta?.locale) || DEFAULT_LOCALE;
262
+ }
263
+
264
+ function isOperaInstalled(control) {
265
+ return control.meta?.opera?.installed === true;
266
+ }
267
+
268
+ function getOperaVersion(control) {
269
+ return control.meta?.opera?.version || null;
270
+ }
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
+
283
+ function loadControl(contextOrRoot) {
284
+ const filePath = controlFilePath(contextOrRoot);
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;
299
+ }
300
+
301
+ function saveControl(contextOrRoot, control, options) {
302
+ control.meta = control.meta || {};
303
+ if (!(options && options.skipTimestamp)) {
304
+ control.meta.updatedAt = new Date().toISOString();
305
+ }
306
+ const filePath = controlFilePath(contextOrRoot);
307
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
308
+ fs.writeFileSync(filePath, JSON.stringify(control, null, 2) + "\n", "utf8");
309
+ }
310
+
311
+ function loadWorkspaceManifest(contextOrRoot) {
312
+ const context = ensureContext(contextOrRoot);
313
+ if (!context.manifestFile || !fileExists(context.manifestFile)) return null;
314
+ return readJson(context.manifestFile, null);
315
+ }
316
+
317
+ function saveWorkspaceManifest(contextOrRoot, manifest) {
318
+ const context = ensureContext(contextOrRoot);
319
+ const manifestFile = context.manifestFile || path.join(context.workspaceRoot, WORKSPACE_MANIFEST);
320
+ fs.writeFileSync(manifestFile, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
321
+ }
322
+
323
+ module.exports = {
324
+ DEFAULT_PHASES,
325
+ DEFAULT_PHASE_IDS,
326
+ DEFAULT_LOCALE,
327
+ DEFAULT_APP_DIR,
328
+ DEFAULT_OPS_DIR,
329
+ DEFAULT_DEV_BRANCH,
330
+ DEFAULT_PUBLISH_BRANCH,
331
+ WORKSPACE_MANIFEST,
332
+ buildDefaultPhases,
333
+ createSplitContext,
334
+ createLegacyContext,
335
+ resolveWorkspaceContext,
336
+ resolveProjectRoot,
337
+ ensureContext,
338
+ controlFilePath,
339
+ runtimeFilePath,
340
+ docFilePaths,
341
+ envFilePaths,
342
+ packageFilePath,
343
+ workspaceManifestPath,
344
+ loadWorkspaceManifest,
345
+ saveWorkspaceManifest,
346
+ getPhases,
347
+ getLocale,
348
+ isOperaInstalled,
349
+ getOperaVersion,
350
+ loadControl,
351
+ saveControl,
352
+ };