trackops 2.0.3 → 2.0.5

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 (103) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +695 -402
  3. package/bin/trackops.js +116 -116
  4. package/lib/config.js +326 -326
  5. package/lib/control.js +208 -208
  6. package/lib/env.js +244 -244
  7. package/lib/init.js +325 -325
  8. package/lib/locale.js +24 -0
  9. package/lib/opera-bootstrap.js +941 -874
  10. package/lib/opera.js +494 -477
  11. package/lib/preferences.js +74 -74
  12. package/lib/registry.js +214 -196
  13. package/lib/release.js +56 -56
  14. package/lib/runtime-state.js +144 -144
  15. package/lib/server.js +312 -207
  16. package/lib/skills.js +74 -57
  17. package/lib/workspace.js +260 -260
  18. package/locales/en.json +192 -166
  19. package/locales/es.json +192 -166
  20. package/package.json +61 -58
  21. package/scripts/postinstall-locale.js +21 -21
  22. package/scripts/skills-marketplace-smoke.js +124 -124
  23. package/scripts/smoke-tests.js +558 -554
  24. package/scripts/sync-skill-version.js +21 -21
  25. package/scripts/validate-skill.js +103 -103
  26. package/skills/trackops/SKILL.md +126 -122
  27. package/skills/trackops/agents/openai.yaml +7 -7
  28. package/skills/trackops/locales/en/SKILL.md +126 -122
  29. package/skills/trackops/locales/en/references/activation.md +94 -75
  30. package/skills/trackops/locales/en/references/troubleshooting.md +73 -55
  31. package/skills/trackops/locales/en/references/workflow.md +55 -32
  32. package/skills/trackops/references/activation.md +94 -75
  33. package/skills/trackops/references/troubleshooting.md +73 -55
  34. package/skills/trackops/references/workflow.md +55 -32
  35. package/skills/trackops/skill.json +29 -29
  36. package/templates/hooks/post-checkout +2 -2
  37. package/templates/hooks/post-commit +2 -2
  38. package/templates/hooks/post-merge +2 -2
  39. package/templates/opera/agent.md +28 -27
  40. package/templates/opera/architecture/dependency-graph.md +24 -24
  41. package/templates/opera/architecture/runtime-automation.md +24 -24
  42. package/templates/opera/architecture/runtime-operations.md +34 -34
  43. package/templates/opera/en/agent.md +22 -21
  44. package/templates/opera/en/architecture/dependency-graph.md +24 -24
  45. package/templates/opera/en/architecture/runtime-automation.md +24 -24
  46. package/templates/opera/en/architecture/runtime-operations.md +34 -34
  47. package/templates/opera/en/reviews/delivery-audit.md +18 -18
  48. package/templates/opera/en/reviews/integration-audit.md +18 -18
  49. package/templates/opera/en/router.md +24 -19
  50. package/templates/opera/references/autonomy-and-recovery.md +117 -117
  51. package/templates/opera/references/opera-cycle.md +193 -193
  52. package/templates/opera/registry.md +28 -28
  53. package/templates/opera/reviews/delivery-audit.md +18 -18
  54. package/templates/opera/reviews/integration-audit.md +18 -18
  55. package/templates/opera/router.md +54 -49
  56. package/templates/skills/changelog-updater/SKILL.md +69 -69
  57. package/templates/skills/commiter/SKILL.md +99 -99
  58. package/templates/skills/opera-contract-auditor/SKILL.md +38 -38
  59. package/templates/skills/opera-contract-auditor/locales/en/SKILL.md +38 -38
  60. package/templates/skills/opera-policy-guard/SKILL.md +26 -26
  61. package/templates/skills/opera-policy-guard/locales/en/SKILL.md +26 -26
  62. package/templates/skills/opera-skill/SKILL.md +279 -0
  63. package/templates/skills/opera-skill/locales/en/SKILL.md +279 -0
  64. package/templates/skills/opera-skill/locales/en/references/phase-dod.md +138 -0
  65. package/templates/skills/opera-skill/references/phase-dod.md +138 -0
  66. package/templates/skills/project-starter-skill/SKILL.md +150 -131
  67. package/templates/skills/project-starter-skill/locales/en/SKILL.md +143 -105
  68. package/templates/skills/project-starter-skill/references/opera-cycle.md +195 -193
  69. package/ui/css/base.css +284 -266
  70. package/ui/css/charts.css +425 -327
  71. package/ui/css/components.css +1107 -570
  72. package/ui/css/onboarding.css +133 -0
  73. package/ui/css/panels.css +345 -406
  74. package/ui/css/terminal.css +125 -0
  75. package/ui/css/timeline.css +58 -0
  76. package/ui/css/tokens.css +284 -227
  77. package/ui/favicon.svg +5 -5
  78. package/ui/index.html +99 -96
  79. package/ui/js/api.js +49 -13
  80. package/ui/js/app.js +28 -32
  81. package/ui/js/charts.js +526 -0
  82. package/ui/js/console-logger.js +172 -172
  83. package/ui/js/filters.js +247 -0
  84. package/ui/js/icons.js +129 -104
  85. package/ui/js/keyboard.js +229 -0
  86. package/ui/js/onboarding.js +33 -42
  87. package/ui/js/router.js +142 -125
  88. package/ui/js/theme.js +100 -100
  89. package/ui/js/time-tracker.js +248 -248
  90. package/ui/js/views/board.js +84 -114
  91. package/ui/js/views/dashboard.js +870 -0
  92. package/ui/js/views/flash.js +47 -47
  93. package/ui/js/views/projects.js +745 -0
  94. package/ui/js/views/scrum.js +476 -0
  95. package/ui/js/views/settings.js +153 -203
  96. package/ui/js/views/sidebar.js +37 -31
  97. package/ui/js/views/tasks.js +218 -101
  98. package/ui/js/views/timeline.js +265 -0
  99. package/ui/js/views/topbar.js +94 -107
  100. package/ui/app.js +0 -950
  101. package/ui/js/views/insights.js +0 -340
  102. package/ui/js/views/overview.js +0 -369
  103. package/ui/styles.css +0 -688
package/lib/config.js CHANGED
@@ -1,326 +1,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 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 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
+ };