trackops 2.0.4 → 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 (90) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +695 -640
  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 +41 -41
  9. package/lib/opera-bootstrap.js +942 -936
  10. package/lib/opera.js +495 -486
  11. package/lib/preferences.js +74 -74
  12. package/lib/registry.js +214 -214
  13. package/lib/release.js +56 -56
  14. package/lib/runtime-state.js +144 -144
  15. package/lib/skills.js +74 -57
  16. package/lib/workspace.js +260 -260
  17. package/locales/en.json +192 -170
  18. package/locales/es.json +192 -170
  19. package/package.json +61 -58
  20. package/scripts/postinstall-locale.js +21 -21
  21. package/scripts/skills-marketplace-smoke.js +124 -124
  22. package/scripts/smoke-tests.js +558 -554
  23. package/scripts/sync-skill-version.js +21 -21
  24. package/scripts/validate-skill.js +103 -103
  25. package/skills/trackops/SKILL.md +126 -122
  26. package/skills/trackops/agents/openai.yaml +7 -7
  27. package/skills/trackops/locales/en/SKILL.md +126 -122
  28. package/skills/trackops/locales/en/references/activation.md +94 -90
  29. package/skills/trackops/locales/en/references/troubleshooting.md +73 -67
  30. package/skills/trackops/locales/en/references/workflow.md +55 -32
  31. package/skills/trackops/references/activation.md +94 -90
  32. package/skills/trackops/references/troubleshooting.md +73 -67
  33. package/skills/trackops/references/workflow.md +55 -32
  34. package/skills/trackops/skill.json +29 -29
  35. package/templates/hooks/post-checkout +2 -2
  36. package/templates/hooks/post-commit +2 -2
  37. package/templates/hooks/post-merge +2 -2
  38. package/templates/opera/agent.md +28 -27
  39. package/templates/opera/architecture/dependency-graph.md +24 -24
  40. package/templates/opera/architecture/runtime-automation.md +24 -24
  41. package/templates/opera/architecture/runtime-operations.md +34 -34
  42. package/templates/opera/en/agent.md +22 -21
  43. package/templates/opera/en/architecture/dependency-graph.md +24 -24
  44. package/templates/opera/en/architecture/runtime-automation.md +24 -24
  45. package/templates/opera/en/architecture/runtime-operations.md +34 -34
  46. package/templates/opera/en/reviews/delivery-audit.md +18 -18
  47. package/templates/opera/en/reviews/integration-audit.md +18 -18
  48. package/templates/opera/en/router.md +24 -19
  49. package/templates/opera/references/autonomy-and-recovery.md +117 -117
  50. package/templates/opera/references/opera-cycle.md +193 -193
  51. package/templates/opera/registry.md +28 -28
  52. package/templates/opera/reviews/delivery-audit.md +18 -18
  53. package/templates/opera/reviews/integration-audit.md +18 -18
  54. package/templates/opera/router.md +54 -49
  55. package/templates/skills/changelog-updater/SKILL.md +69 -69
  56. package/templates/skills/commiter/SKILL.md +99 -99
  57. package/templates/skills/opera-contract-auditor/SKILL.md +38 -38
  58. package/templates/skills/opera-contract-auditor/locales/en/SKILL.md +38 -38
  59. package/templates/skills/opera-policy-guard/SKILL.md +26 -26
  60. package/templates/skills/opera-policy-guard/locales/en/SKILL.md +26 -26
  61. package/templates/skills/opera-skill/SKILL.md +279 -0
  62. package/templates/skills/opera-skill/locales/en/SKILL.md +279 -0
  63. package/templates/skills/opera-skill/locales/en/references/phase-dod.md +138 -0
  64. package/templates/skills/opera-skill/references/phase-dod.md +138 -0
  65. package/templates/skills/project-starter-skill/SKILL.md +150 -131
  66. package/templates/skills/project-starter-skill/locales/en/SKILL.md +143 -105
  67. package/templates/skills/project-starter-skill/references/opera-cycle.md +195 -193
  68. package/ui/css/base.css +284 -284
  69. package/ui/css/charts.css +425 -425
  70. package/ui/css/components.css +1107 -1107
  71. package/ui/css/onboarding.css +133 -133
  72. package/ui/css/terminal.css +125 -125
  73. package/ui/css/timeline.css +58 -58
  74. package/ui/css/tokens.css +284 -284
  75. package/ui/favicon.svg +5 -5
  76. package/ui/index.html +99 -99
  77. package/ui/js/charts.js +526 -526
  78. package/ui/js/console-logger.js +172 -172
  79. package/ui/js/filters.js +247 -247
  80. package/ui/js/icons.js +129 -129
  81. package/ui/js/keyboard.js +229 -229
  82. package/ui/js/router.js +142 -142
  83. package/ui/js/theme.js +100 -100
  84. package/ui/js/time-tracker.js +248 -248
  85. package/ui/js/views/dashboard.js +870 -870
  86. package/ui/js/views/flash.js +47 -47
  87. package/ui/js/views/projects.js +745 -745
  88. package/ui/js/views/scrum.js +476 -476
  89. package/ui/js/views/settings.js +331 -331
  90. package/ui/js/views/timeline.js +265 -265
package/lib/opera.js CHANGED
@@ -3,26 +3,26 @@
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
5
 
6
- const config = require("./config");
7
- const env = require("./env");
8
- const { t, setLocale } = require("./i18n");
9
- const { promptForLocale, maybePromptForLocale, resolveLocale } = require("./locale");
10
- const { resolveLocalizedFile, resolveLocalizedDir, resolveSkillFile } = require("./resources");
11
- const bootstrap = require("./opera-bootstrap");
12
- const runtimeState = require("./runtime-state");
13
-
14
- const TEMPLATES_DIR = path.join(__dirname, "..", "templates", "opera");
15
- const SKILLS_TEMPLATES_DIR = path.join(__dirname, "..", "templates", "skills");
16
- const OPERA_VERSION = require("../package.json").version;
17
- const AUXILIARY_SKILLS = ["project-starter-skill", "opera-contract-auditor", "opera-policy-guard"];
18
-
19
- function nowIso() {
20
- return new Date().toISOString();
21
- }
22
-
23
- function formatLocaleSource(source) {
24
- return t(`locale.source.${String(source || "").trim()}`) || source || t("locale.none");
25
- }
6
+ const config = require("./config");
7
+ const env = require("./env");
8
+ const { t, setLocale } = require("./i18n");
9
+ const { promptForLocale, maybePromptForLocale, resolveLocale } = require("./locale");
10
+ const { resolveLocalizedFile, resolveLocalizedDir, resolveSkillFile } = require("./resources");
11
+ const bootstrap = require("./opera-bootstrap");
12
+ const runtimeState = require("./runtime-state");
13
+
14
+ const TEMPLATES_DIR = path.join(__dirname, "..", "templates", "opera");
15
+ const SKILLS_TEMPLATES_DIR = path.join(__dirname, "..", "templates", "skills");
16
+ const OPERA_VERSION = require("../package.json").version;
17
+ const AUXILIARY_SKILLS = ["opera-skill", "project-starter-skill", "opera-contract-auditor", "opera-policy-guard"];
18
+
19
+ function nowIso() {
20
+ return new Date().toISOString();
21
+ }
22
+
23
+ function formatLocaleSource(source) {
24
+ return t(`locale.source.${String(source || "").trim()}`) || source || t("locale.none");
25
+ }
26
26
 
27
27
  function readText(filePath) {
28
28
  return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
@@ -53,42 +53,58 @@ function copyTemplate(templatePath, targetPath, replacements, options = {}) {
53
53
  return true;
54
54
  }
55
55
 
56
- function copyDirRecursive(src, dest) {
57
- if (!src || !fs.existsSync(src)) return;
58
- fs.mkdirSync(dest, { recursive: true });
59
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
60
- const srcPath = path.join(src, entry.name);
56
+ function copyDirRecursive(src, dest) {
57
+ if (!src || !fs.existsSync(src)) return;
58
+ fs.mkdirSync(dest, { recursive: true });
59
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
60
+ const srcPath = path.join(src, entry.name);
61
61
  const destPath = path.join(dest, entry.name);
62
62
  if (entry.isDirectory()) {
63
63
  copyDirRecursive(srcPath, destPath);
64
64
  } else {
65
65
  fs.copyFileSync(srcPath, destPath);
66
- }
67
- }
68
- }
69
-
70
- function seedDirRecursive(src, dest, options = {}) {
71
- if (!src || !fs.existsSync(src)) return;
72
- fs.mkdirSync(dest, { recursive: true });
73
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
74
- const srcPath = path.join(src, entry.name);
75
- const destPath = path.join(dest, entry.name);
76
- if (entry.isDirectory()) {
77
- seedDirRecursive(srcPath, destPath, options);
78
- } else if (options.overwrite || !fs.existsSync(destPath)) {
79
- fs.mkdirSync(path.dirname(destPath), { recursive: true });
80
- fs.copyFileSync(srcPath, destPath);
81
- }
82
- }
83
- }
84
-
85
- function resolveOperaLocale(control, options = {}) {
86
- return resolveLocale(options.locale, config.getLocale(control) || runtimeState.getGlobalLocale());
87
- }
88
-
89
- function installStructure(root, control, locale, options = {}) {
90
- const context = config.ensureContext(root);
91
- const projectName = control.meta.projectName || "Project";
66
+ }
67
+ }
68
+ }
69
+
70
+ function seedDirRecursive(src, dest, options = {}) {
71
+ if (!src || !fs.existsSync(src)) return;
72
+ fs.mkdirSync(dest, { recursive: true });
73
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
74
+ const srcPath = path.join(src, entry.name);
75
+ const destPath = path.join(dest, entry.name);
76
+ if (entry.isDirectory()) {
77
+ seedDirRecursive(srcPath, destPath, options);
78
+ } else if (options.overwrite || !fs.existsSync(destPath)) {
79
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
80
+ fs.copyFileSync(srcPath, destPath);
81
+ }
82
+ }
83
+ }
84
+
85
+ function resolveOperaLocale(control, options = {}) {
86
+ return resolveLocale(options.locale, config.getLocale(control) || runtimeState.getGlobalLocale());
87
+ }
88
+
89
+ function seedAuxiliarySkill(skillName, locale, skillsDir, options = {}) {
90
+ const templateDir = path.join(SKILLS_TEMPLATES_DIR, skillName);
91
+ if (!fs.existsSync(templateDir)) return;
92
+ const targetDir = path.join(skillsDir, skillName);
93
+ seedDirRecursive(templateDir, targetDir, {
94
+ overwrite: options.rewriteLocalizedTemplates === true,
95
+ });
96
+ const localizedSkill = resolveSkillFile(SKILLS_TEMPLATES_DIR, skillName, locale);
97
+ if (localizedSkill) {
98
+ const targetSkill = path.join(targetDir, "SKILL.md");
99
+ if (!fs.existsSync(targetSkill) || options.rewriteLocalizedTemplates === true) {
100
+ fs.copyFileSync(localizedSkill, targetSkill);
101
+ }
102
+ }
103
+ }
104
+
105
+ function installStructure(root, control, locale, options = {}) {
106
+ const context = config.ensureContext(root);
107
+ const projectName = control.meta.projectName || "Project";
92
108
  const replacements = {
93
109
  PROJECT_NAME: projectName,
94
110
  DESIRED_OUTCOME: t("bootstrap.pendingValue"),
@@ -108,13 +124,13 @@ function installStructure(root, control, locale, options = {}) {
108
124
  overwriteIfTemplate: options.rewriteLocalizedTemplates === true,
109
125
  };
110
126
 
111
- const agentHubDir = context.paths.agentHubDir;
112
- fs.mkdirSync(agentHubDir, { recursive: true });
113
- fs.mkdirSync(context.paths.architectureDir, { recursive: true });
114
- fs.mkdirSync(context.paths.contractDir, { recursive: true });
115
- fs.mkdirSync(context.paths.policyDir, { recursive: true });
116
- fs.mkdirSync(context.paths.bootstrapDir, { recursive: true });
117
- fs.mkdirSync(context.paths.reviewsDir, { recursive: true });
127
+ const agentHubDir = context.paths.agentHubDir;
128
+ fs.mkdirSync(agentHubDir, { recursive: true });
129
+ fs.mkdirSync(context.paths.architectureDir, { recursive: true });
130
+ fs.mkdirSync(context.paths.contractDir, { recursive: true });
131
+ fs.mkdirSync(context.paths.policyDir, { recursive: true });
132
+ fs.mkdirSync(context.paths.bootstrapDir, { recursive: true });
133
+ fs.mkdirSync(context.paths.reviewsDir, { recursive: true });
118
134
 
119
135
  copyTemplate(
120
136
  resolveLocalizedFile(TEMPLATES_DIR, locale, "agent.md"),
@@ -129,8 +145,8 @@ function installStructure(root, control, locale, options = {}) {
129
145
  overwriteOptions,
130
146
  );
131
147
 
132
- const skillsRegistryDir = context.paths.skillsDir;
133
- fs.mkdirSync(skillsRegistryDir, { recursive: true });
148
+ const skillsRegistryDir = context.paths.skillsDir;
149
+ fs.mkdirSync(skillsRegistryDir, { recursive: true });
134
150
  copyTemplate(
135
151
  resolveLocalizedFile(TEMPLATES_DIR, locale, "registry.md"),
136
152
  path.join(skillsRegistryDir, "_registry.md"),
@@ -139,79 +155,72 @@ function installStructure(root, control, locale, options = {}) {
139
155
  );
140
156
 
141
157
  const genesisTemplatePath = resolveLocalizedFile(TEMPLATES_DIR, locale, "genesis.md");
142
- const genesisPath = context.paths.genesisFile;
158
+ const genesisPath = context.paths.genesisFile;
143
159
  if (!fs.existsSync(genesisPath) || bootstrap.isVirginGenesis(readText(genesisPath))) {
144
160
  copyTemplate(genesisTemplatePath, genesisPath, replacements, { overwrite: true });
145
161
  }
146
162
 
147
- const refsTemplateDir = resolveLocalizedDir(TEMPLATES_DIR, locale, "references");
148
- const refsTargetDir = path.join(context.paths.skillsDir, "project-starter-skill", "references");
149
- seedDirRecursive(refsTemplateDir, refsTargetDir, {
150
- overwrite: options.rewriteLocalizedTemplates === true,
151
- });
152
-
153
- const architectureTemplateDir = resolveLocalizedDir(TEMPLATES_DIR, locale, "architecture");
154
- seedDirRecursive(architectureTemplateDir, context.paths.architectureDir, {
155
- overwrite: options.rewriteLocalizedTemplates === true,
156
- });
157
-
158
- const reviewsTemplateDir = resolveLocalizedDir(TEMPLATES_DIR, locale, "reviews");
159
- seedDirRecursive(reviewsTemplateDir, context.paths.reviewsDir, {
160
- overwrite: options.rewriteLocalizedTemplates === true,
161
- });
162
-
163
- for (const skillName of AUXILIARY_SKILLS) {
164
- const skillTarget = path.join(context.paths.skillsDir, skillName, "SKILL.md");
165
- const skillTemplate = resolveSkillFile(SKILLS_TEMPLATES_DIR, skillName, locale);
166
- if (skillTemplate) {
167
- fs.mkdirSync(path.dirname(skillTarget), { recursive: true });
168
- if (!fs.existsSync(skillTarget) || options.rewriteLocalizedTemplates === true) {
169
- fs.copyFileSync(skillTemplate, skillTarget);
170
- }
171
- }
172
- }
173
-
174
- bootstrap.writeAutonomyPolicy(context);
175
- }
176
-
177
- async function install(root, options = {}) {
178
- const context = config.ensureContext(root);
179
- const controlFile = config.controlFilePath(context);
180
- if (!fs.existsSync(controlFile)) {
181
- throw new Error("project_control.json not found. Run 'trackops init' first.");
182
- }
183
-
184
- const control = config.loadControl(context);
185
- let locale = resolveOperaLocale(control, options);
186
- if (!options.locale && !control.meta?.locale && !runtimeState.getGlobalLocale()) {
187
- locale = await promptForLocale(locale);
188
- if (!runtimeState.getGlobalLocale()) {
189
- await runtimeState.ensureGlobalLocale({ preferredLocale: locale, interactive: false });
190
- }
191
- } else if (!options.locale) {
192
- locale = await maybePromptForLocale(locale, { promptMode: "always" });
193
- }
194
- control.meta.locale = locale;
195
- setLocale(locale);
196
-
197
- const alreadyInstalled = config.isOperaInstalled(control);
198
- installStructure(context, control, locale);
199
-
200
- control.meta.opera = {
201
- ...(control.meta.opera || {}),
202
- installed: true,
203
- model: "v3",
204
- stableTag: "stable",
205
- version: OPERA_VERSION,
206
- installedAt: control.meta?.opera?.installedAt || nowIso(),
207
- skills: control.meta?.opera?.skills || [],
208
- legacyStatus: "supported",
209
- };
210
- if (!control.meta.opera.bootstrap && options.bootstrap === false) {
211
- control.meta.opera.bootstrap = bootstrap.createAwaitingBootstrapState(context);
212
- }
213
- config.saveControl(context, control);
214
- env.syncEnvironment(context, control);
163
+ const refsTemplateDir = resolveLocalizedDir(TEMPLATES_DIR, locale, "references");
164
+ const refsTargetDir = path.join(context.paths.skillsDir, "project-starter-skill", "references");
165
+ seedDirRecursive(refsTemplateDir, refsTargetDir, {
166
+ overwrite: options.rewriteLocalizedTemplates === true,
167
+ });
168
+
169
+ const architectureTemplateDir = resolveLocalizedDir(TEMPLATES_DIR, locale, "architecture");
170
+ seedDirRecursive(architectureTemplateDir, context.paths.architectureDir, {
171
+ overwrite: options.rewriteLocalizedTemplates === true,
172
+ });
173
+
174
+ const reviewsTemplateDir = resolveLocalizedDir(TEMPLATES_DIR, locale, "reviews");
175
+ seedDirRecursive(reviewsTemplateDir, context.paths.reviewsDir, {
176
+ overwrite: options.rewriteLocalizedTemplates === true,
177
+ });
178
+
179
+ for (const skillName of AUXILIARY_SKILLS) {
180
+ seedAuxiliarySkill(skillName, locale, context.paths.skillsDir, options);
181
+ }
182
+
183
+ bootstrap.writeAutonomyPolicy(context);
184
+ }
185
+
186
+ async function install(root, options = {}) {
187
+ const context = config.ensureContext(root);
188
+ const controlFile = config.controlFilePath(context);
189
+ if (!fs.existsSync(controlFile)) {
190
+ throw new Error("project_control.json not found. Run 'trackops init' first.");
191
+ }
192
+
193
+ const control = config.loadControl(context);
194
+ let locale = resolveOperaLocale(control, options);
195
+ if (!options.locale && !control.meta?.locale && !runtimeState.getGlobalLocale()) {
196
+ locale = await promptForLocale(locale);
197
+ if (!runtimeState.getGlobalLocale()) {
198
+ await runtimeState.ensureGlobalLocale({ preferredLocale: locale, interactive: false });
199
+ }
200
+ } else if (!options.locale) {
201
+ locale = await maybePromptForLocale(locale, { promptMode: "always" });
202
+ }
203
+ control.meta.locale = locale;
204
+ setLocale(locale);
205
+
206
+ const alreadyInstalled = config.isOperaInstalled(control);
207
+ installStructure(context, control, locale);
208
+
209
+ control.meta.opera = {
210
+ ...(control.meta.opera || {}),
211
+ installed: true,
212
+ model: "v3",
213
+ stableTag: "stable",
214
+ version: OPERA_VERSION,
215
+ installedAt: control.meta?.opera?.installedAt || nowIso(),
216
+ skills: control.meta?.opera?.skills || [],
217
+ legacyStatus: "supported",
218
+ };
219
+ if (!control.meta.opera.bootstrap && options.bootstrap === false) {
220
+ control.meta.opera.bootstrap = bootstrap.createAwaitingBootstrapState(context);
221
+ }
222
+ config.saveControl(context, control);
223
+ env.syncEnvironment(context, control);
215
224
 
216
225
  if (!alreadyInstalled) {
217
226
  console.log(t("opera.installed", { version: OPERA_VERSION }));
@@ -219,176 +228,176 @@ async function install(root, options = {}) {
219
228
  console.log(t("opera.alreadyInstalled", { version: config.getOperaVersion(control) || OPERA_VERSION }));
220
229
  }
221
230
 
222
- const skills = require("./skills");
223
- for (const skillName of ["commiter", "changelog-updater"]) {
224
- try {
225
- skills.installSkill(context, skillName, { locale });
226
- } catch (_error) {
227
- // ignore
228
- }
229
- }
230
- skills.updateRegistry(context);
231
-
232
- if (options.bootstrap !== false) {
233
- await runBootstrap(context, {
234
- locale,
235
- answers: options.answers,
236
- interactive: options.interactive,
237
- bootstrapMode: options.bootstrapMode,
238
- technicalLevel: options.technicalLevel,
239
- projectState: options.projectState,
240
- docsState: options.docsState,
241
- decisionOwnership: options.decisionOwnership,
242
- });
243
- }
244
- }
245
-
246
- function removePath(targetPath) {
247
- if (!targetPath || !fs.existsSync(targetPath)) return;
248
- fs.rmSync(targetPath, { recursive: true, force: true });
249
- }
250
-
251
- function backupManagedArtifacts(context) {
252
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
253
- const backupRoot = path.join(context.paths.tmpDir, "upgrade-backups", timestamp);
254
- const items = [
255
- context.paths.agentHubDir,
256
- context.paths.skillsDir,
257
- context.paths.genesisFile,
258
- context.paths.contractFile,
259
- context.paths.autonomyPolicyFile,
260
- context.paths.bootstrapDir,
261
- ];
262
- fs.mkdirSync(backupRoot, { recursive: true });
263
- let copied = 0;
264
- for (const item of items) {
265
- if (!fs.existsSync(item)) continue;
266
- const relative = path.relative(context.workspaceRoot, item);
267
- const destination = path.join(backupRoot, relative);
268
- if (fs.statSync(item).isDirectory()) {
269
- copyDirRecursive(item, destination);
270
- } else {
271
- fs.mkdirSync(path.dirname(destination), { recursive: true });
272
- fs.copyFileSync(item, destination);
273
- }
274
- copied += 1;
275
- }
276
- return { backupRoot, copied };
277
- }
278
-
279
- async function runBootstrap(root, options = {}) {
280
- const context = config.ensureContext(root);
281
- const control = config.loadControl(context);
282
- const locale = resolveOperaLocale(control, options);
231
+ const skills = require("./skills");
232
+ for (const skillName of ["commiter", "changelog-updater"]) {
233
+ try {
234
+ skills.installSkill(context, skillName, { locale });
235
+ } catch (_error) {
236
+ // ignore
237
+ }
238
+ }
239
+ skills.updateRegistry(context);
240
+
241
+ if (options.bootstrap !== false) {
242
+ await runBootstrap(context, {
243
+ locale,
244
+ answers: options.answers,
245
+ interactive: options.interactive,
246
+ bootstrapMode: options.bootstrapMode,
247
+ technicalLevel: options.technicalLevel,
248
+ projectState: options.projectState,
249
+ docsState: options.docsState,
250
+ decisionOwnership: options.decisionOwnership,
251
+ });
252
+ }
253
+ }
254
+
255
+ function removePath(targetPath) {
256
+ if (!targetPath || !fs.existsSync(targetPath)) return;
257
+ fs.rmSync(targetPath, { recursive: true, force: true });
258
+ }
259
+
260
+ function backupManagedArtifacts(context) {
261
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
262
+ const backupRoot = path.join(context.paths.tmpDir, "upgrade-backups", timestamp);
263
+ const items = [
264
+ context.paths.agentHubDir,
265
+ context.paths.skillsDir,
266
+ context.paths.genesisFile,
267
+ context.paths.contractFile,
268
+ context.paths.autonomyPolicyFile,
269
+ context.paths.bootstrapDir,
270
+ ];
271
+ fs.mkdirSync(backupRoot, { recursive: true });
272
+ let copied = 0;
273
+ for (const item of items) {
274
+ if (!fs.existsSync(item)) continue;
275
+ const relative = path.relative(context.workspaceRoot, item);
276
+ const destination = path.join(backupRoot, relative);
277
+ if (fs.statSync(item).isDirectory()) {
278
+ copyDirRecursive(item, destination);
279
+ } else {
280
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
281
+ fs.copyFileSync(item, destination);
282
+ }
283
+ copied += 1;
284
+ }
285
+ return { backupRoot, copied };
286
+ }
287
+
288
+ async function runBootstrap(root, options = {}) {
289
+ const context = config.ensureContext(root);
290
+ const control = config.loadControl(context);
291
+ const locale = resolveOperaLocale(control, options);
283
292
  control.meta.locale = locale;
284
293
  setLocale(locale);
285
294
 
286
- const legacyBootstrap = bootstrap.detectLegacyBootstrap(context, control);
287
- if (legacyBootstrap && !control.meta.opera?.bootstrap) {
288
- control.meta.opera = control.meta.opera || {};
289
- control.meta.opera.bootstrap = legacyBootstrap;
290
- config.saveControl(context, control);
291
- }
292
-
293
- if ((options.resume || options.forceResume) && control.meta?.opera?.bootstrap) {
294
- const resumed = bootstrap.resumeBootstrap(context, control);
295
- if (resumed.resumed) {
296
- const updatedControl = bootstrap.applyBootstrap(context, control, resumed.profile);
297
- env.syncEnvironment(context, updatedControl, { requiredKeys: env.inferRequiredKeys(updatedControl, context) });
298
- const ops = require("./control");
299
- ops.syncDocs(context, updatedControl);
300
- ops.refreshRepoRuntime(context, { quiet: true });
301
- console.log(t("bootstrap.completed"));
302
- return resumed.profile;
303
- }
304
- console.log(t("bootstrap.awaitingAgent"));
305
- return control.meta.opera.bootstrap;
306
- }
307
-
308
- const profile = await bootstrap.collectBootstrapProfile(context, control, options);
309
- const updatedControl = bootstrap.applyBootstrap(context, control, profile);
310
- env.syncEnvironment(context, updatedControl, { requiredKeys: env.inferRequiredKeys(updatedControl, context) });
311
- const ops = require("./control");
312
- ops.syncDocs(context, updatedControl);
313
- ops.refreshRepoRuntime(context, { quiet: true });
314
-
315
- if (profile.mode === "agent_handoff") {
316
- console.log(t("bootstrap.awaitingAgent"));
317
- console.log(`${t("bootstrap.handoffFile")}: ${profile.handoffFiles?.markdown || bootstrap.bootstrapRelativePaths(context).markdown}`);
318
- console.log(t("bootstrap.next.handoff"));
319
- } else {
320
- console.log(profile.status === "completed" ? t("bootstrap.completed") : t("bootstrap.pending"));
321
- console.log(
322
- profile.status === "completed"
323
- ? t("bootstrap.next.directCompleted")
324
- : t("bootstrap.next.directPending"),
325
- );
326
- }
327
- return profile;
328
- }
329
-
330
- function status(root) {
331
- const context = config.ensureContext(root);
332
- const control = config.loadControl(context);
333
- setLocale(config.getLocale(control));
295
+ const legacyBootstrap = bootstrap.detectLegacyBootstrap(context, control);
296
+ if (legacyBootstrap && !control.meta.opera?.bootstrap) {
297
+ control.meta.opera = control.meta.opera || {};
298
+ control.meta.opera.bootstrap = legacyBootstrap;
299
+ config.saveControl(context, control);
300
+ }
301
+
302
+ if ((options.resume || options.forceResume) && control.meta?.opera?.bootstrap) {
303
+ const resumed = bootstrap.resumeBootstrap(context, control);
304
+ if (resumed.resumed) {
305
+ const updatedControl = bootstrap.applyBootstrap(context, control, resumed.profile);
306
+ env.syncEnvironment(context, updatedControl, { requiredKeys: env.inferRequiredKeys(updatedControl, context) });
307
+ const ops = require("./control");
308
+ ops.syncDocs(context, updatedControl);
309
+ ops.refreshRepoRuntime(context, { quiet: true });
310
+ console.log(t("bootstrap.completed"));
311
+ return resumed.profile;
312
+ }
313
+ console.log(t("bootstrap.awaitingAgent"));
314
+ return control.meta.opera.bootstrap;
315
+ }
316
+
317
+ const profile = await bootstrap.collectBootstrapProfile(context, control, options);
318
+ const updatedControl = bootstrap.applyBootstrap(context, control, profile);
319
+ env.syncEnvironment(context, updatedControl, { requiredKeys: env.inferRequiredKeys(updatedControl, context) });
320
+ const ops = require("./control");
321
+ ops.syncDocs(context, updatedControl);
322
+ ops.refreshRepoRuntime(context, { quiet: true });
323
+
324
+ if (profile.mode === "agent_handoff") {
325
+ console.log(t("bootstrap.awaitingAgent"));
326
+ console.log(`${t("bootstrap.handoffFile")}: ${profile.handoffFiles?.markdown || bootstrap.bootstrapRelativePaths(context).markdown}`);
327
+ console.log(t("bootstrap.next.handoff"));
328
+ } else {
329
+ console.log(profile.status === "completed" ? t("bootstrap.completed") : t("bootstrap.pending"));
330
+ console.log(
331
+ profile.status === "completed"
332
+ ? t("bootstrap.next.directCompleted")
333
+ : t("bootstrap.next.directPending"),
334
+ );
335
+ }
336
+ return profile;
337
+ }
338
+
339
+ function status(root) {
340
+ const context = config.ensureContext(root);
341
+ const control = config.loadControl(context);
342
+ setLocale(config.getLocale(control));
334
343
 
335
344
  if (!config.isOperaInstalled(control)) {
336
345
  console.log(t("opera.notInstalled"));
337
346
  return;
338
347
  }
339
-
340
- const opera = control.meta.opera;
341
- const bootstrapState = opera.bootstrap || bootstrap.detectLegacyBootstrap(context, control);
342
- const localeDoctor = runtimeState.doctorLocale(control.meta?.locale || null);
343
- console.log(t("opera.status.version", { version: opera.version }));
344
- console.log(t("opera.status.installed", { value: opera.installedAt }));
345
- console.log(t("opera.status.skills", { value: (opera.skills || []).join(", ") || t("locale.none") }));
346
- console.log(t("opera.status.locale", { locale: config.getLocale(control), source: formatLocaleSource(localeDoctor.source) }));
347
- console.log(t("opera.status.legacy", { value: opera.legacyStatus || bootstrapState?.status || "supported" }));
348
- console.log(t("opera.status.contractVersion", { value: opera.contractVersion || t("locale.none") }));
349
- console.log(t("opera.status.contractReadiness", { value: opera.contractReadiness || "hypothesis" }));
350
-
351
- if (bootstrapState) {
352
- console.log(t("opera.status.bootstrap", { value: bootstrapState.status }));
353
- if (bootstrapState.mode) {
354
- console.log(t("opera.status.mode", { value: bootstrapState.mode }));
355
- }
356
- if (bootstrapState.routeReason) {
357
- console.log(t("opera.status.route", { value: bootstrapState.routeReason }));
358
- }
359
- if (bootstrapState.decisionOwnership) {
360
- console.log(t("opera.status.ownership", { value: bootstrapState.decisionOwnership }));
361
- }
362
- if ((bootstrapState.missingFields || []).length) {
363
- console.log(t("opera.status.missing", { value: bootstrapState.missingFields.join(", ") }));
364
- console.log(t("opera.status.resume"));
365
- }
366
- if (bootstrapState.handoffFiles?.markdown) {
367
- console.log(t("opera.status.handoff", { value: bootstrapState.handoffFiles.markdown }));
368
- }
369
- if (bootstrapState.reviewFiles?.qualityReport) {
370
- console.log(t("opera.status.qualityReport", { value: bootstrapState.reviewFiles.qualityReport }));
371
- }
372
- }
373
-
374
- const checks = [
375
- [context.layout === "split" ? "ops/.agent/hub/agent.md" : ".agent/hub/agent.md", fs.existsSync(path.join(context.paths.agentHubDir, "agent.md"))],
376
- [context.layout === "split" ? "ops/.agent/hub/router.md" : ".agent/hub/router.md", fs.existsSync(path.join(context.paths.agentHubDir, "router.md"))],
377
- [context.layout === "split" ? "ops/.agents/skills/_registry.md" : ".agents/skills/_registry.md", fs.existsSync(context.paths.registryPath)],
378
- [context.layout === "split" ? "ops/genesis.md" : "genesis.md", fs.existsSync(context.paths.genesisFile)],
379
- [context.layout === "split" ? "ops/contract/operating-contract.json" : "contract/operating-contract.json", fs.existsSync(context.paths.contractFile)],
380
- [context.layout === "split" ? "ops/policy/autonomy.json" : "policy/autonomy.json", fs.existsSync(context.paths.autonomyPolicyFile)],
381
- ];
382
-
383
- console.log(t("opera.status.structure"));
348
+
349
+ const opera = control.meta.opera;
350
+ const bootstrapState = opera.bootstrap || bootstrap.detectLegacyBootstrap(context, control);
351
+ const localeDoctor = runtimeState.doctorLocale(control.meta?.locale || null);
352
+ console.log(t("opera.status.version", { version: opera.version }));
353
+ console.log(t("opera.status.installed", { value: opera.installedAt }));
354
+ console.log(t("opera.status.skills", { value: (opera.skills || []).join(", ") || t("locale.none") }));
355
+ console.log(t("opera.status.locale", { locale: config.getLocale(control), source: formatLocaleSource(localeDoctor.source) }));
356
+ console.log(t("opera.status.legacy", { value: opera.legacyStatus || bootstrapState?.status || "supported" }));
357
+ console.log(t("opera.status.contractVersion", { value: opera.contractVersion || t("locale.none") }));
358
+ console.log(t("opera.status.contractReadiness", { value: opera.contractReadiness || "hypothesis" }));
359
+
360
+ if (bootstrapState) {
361
+ console.log(t("opera.status.bootstrap", { value: bootstrapState.status }));
362
+ if (bootstrapState.mode) {
363
+ console.log(t("opera.status.mode", { value: bootstrapState.mode }));
364
+ }
365
+ if (bootstrapState.routeReason) {
366
+ console.log(t("opera.status.route", { value: bootstrapState.routeReason }));
367
+ }
368
+ if (bootstrapState.decisionOwnership) {
369
+ console.log(t("opera.status.ownership", { value: bootstrapState.decisionOwnership }));
370
+ }
371
+ if ((bootstrapState.missingFields || []).length) {
372
+ console.log(t("opera.status.missing", { value: bootstrapState.missingFields.join(", ") }));
373
+ console.log(t("opera.status.resume"));
374
+ }
375
+ if (bootstrapState.handoffFiles?.markdown) {
376
+ console.log(t("opera.status.handoff", { value: bootstrapState.handoffFiles.markdown }));
377
+ }
378
+ if (bootstrapState.reviewFiles?.qualityReport) {
379
+ console.log(t("opera.status.qualityReport", { value: bootstrapState.reviewFiles.qualityReport }));
380
+ }
381
+ }
382
+
383
+ const checks = [
384
+ [context.layout === "split" ? "ops/.agent/hub/agent.md" : ".agent/hub/agent.md", fs.existsSync(path.join(context.paths.agentHubDir, "agent.md"))],
385
+ [context.layout === "split" ? "ops/.agent/hub/router.md" : ".agent/hub/router.md", fs.existsSync(path.join(context.paths.agentHubDir, "router.md"))],
386
+ [context.layout === "split" ? "ops/.agents/skills/_registry.md" : ".agents/skills/_registry.md", fs.existsSync(context.paths.registryPath)],
387
+ [context.layout === "split" ? "ops/genesis.md" : "genesis.md", fs.existsSync(context.paths.genesisFile)],
388
+ [context.layout === "split" ? "ops/contract/operating-contract.json" : "contract/operating-contract.json", fs.existsSync(context.paths.contractFile)],
389
+ [context.layout === "split" ? "ops/policy/autonomy.json" : "policy/autonomy.json", fs.existsSync(context.paths.autonomyPolicyFile)],
390
+ ];
391
+
392
+ console.log(t("opera.status.structure"));
384
393
  for (const [file, exists] of checks) {
385
394
  console.log(` ${exists ? "\u2705" : "\u274C"} ${file}`);
386
395
  }
387
396
  }
388
397
 
389
- function configure(root, args) {
390
- const context = config.ensureContext(root);
391
- const control = config.loadControl(context);
398
+ function configure(root, args) {
399
+ const context = config.ensureContext(root);
400
+ const control = config.loadControl(context);
392
401
  setLocale(config.getLocale(control));
393
402
  let nextLocale = config.getLocale(control);
394
403
 
@@ -398,212 +407,212 @@ function configure(root, args) {
398
407
  control.meta.locale = nextLocale;
399
408
  i += 1;
400
409
  }
401
- if (args[i] === "--phases" && args[i + 1]) {
402
- try {
403
- control.meta.phases = JSON.parse(args[i + 1]);
404
- } catch (_e) {
405
- console.error(t("opera.configure.invalidPhases"));
406
- return;
407
- }
410
+ if (args[i] === "--phases" && args[i + 1]) {
411
+ try {
412
+ control.meta.phases = JSON.parse(args[i + 1]);
413
+ } catch (_e) {
414
+ console.error(t("opera.configure.invalidPhases"));
415
+ return;
416
+ }
417
+ i += 1;
418
+ }
419
+ }
420
+
421
+ config.saveControl(context, control);
422
+ if (config.isOperaInstalled(control)) {
423
+ installStructure(context, control, nextLocale, { rewriteLocalizedTemplates: true });
424
+ env.syncEnvironment(context, control);
425
+ const ops = require("./control");
426
+ ops.syncDocs(context, control);
427
+ }
428
+ console.log(t("opera.configure.updated"));
429
+ }
430
+
431
+ function upgrade(root, args = []) {
432
+ const context = config.ensureContext(root);
433
+ const control = config.loadControl(context);
434
+ const locale = config.getLocale(control);
435
+ setLocale(locale);
436
+ const wantsStable = (args || []).includes("--stable");
437
+ const wantsReset = (args || []).includes("--reset");
438
+
439
+ if (!config.isOperaInstalled(control)) {
440
+ console.log(t("opera.notInstalled"));
441
+ console.log(t("opera.upgrade.runInstallFirst"));
442
+ return;
443
+ }
444
+
445
+ if (!wantsStable) {
446
+ console.log(t("opera.upgrade.usage"));
447
+ return;
448
+ }
449
+
450
+ const legacy = bootstrap.detectLegacyBootstrap(context, control);
451
+ const isLegacyUnsupported = legacy?.status === "legacy_unsupported";
452
+ if (isLegacyUnsupported && !wantsReset) {
453
+ control.meta.opera = control.meta.opera || {};
454
+ control.meta.opera.legacyStatus = "legacy_unsupported";
455
+ config.saveControl(context, control);
456
+ console.log(t("opera.upgrade.legacyUnsupported"));
457
+ return;
458
+ }
459
+
460
+ const backup = backupManagedArtifacts(context);
461
+ if (wantsReset) {
462
+ removePath(context.paths.agentHubDir);
463
+ removePath(context.paths.skillsDir);
464
+ removePath(context.paths.genesisFile);
465
+ removePath(context.paths.contractFile);
466
+ removePath(context.paths.autonomyPolicyFile);
467
+ removePath(context.paths.bootstrapDir);
468
+ }
469
+
470
+ installStructure(context, control, locale, { rewriteLocalizedTemplates: true });
471
+ control.meta.opera = {
472
+ ...(control.meta.opera || {}),
473
+ installed: true,
474
+ model: "v3",
475
+ stableTag: "stable",
476
+ version: OPERA_VERSION,
477
+ legacyStatus: "supported",
478
+ contractVersion: fs.existsSync(context.paths.contractFile) ? bootstrap.CONTRACT_VERSION : null,
479
+ contractReadiness: fs.existsSync(context.paths.contractFile)
480
+ ? (control.meta?.opera?.contractReadiness || "verified")
481
+ : "hypothesis",
482
+ bootstrap: wantsReset
483
+ ? bootstrap.createAwaitingBootstrapState(context)
484
+ : (control.meta?.opera?.bootstrap || bootstrap.createAwaitingBootstrapState(context)),
485
+ };
486
+ config.saveControl(context, control);
487
+ env.syncEnvironment(context, control);
488
+ require("./skills").updateRegistry(context);
489
+ console.log(t("opera.upgrade.backup", { path: path.relative(context.workspaceRoot, backup.backupRoot) }));
490
+ console.log(t("opera.upgraded", { version: OPERA_VERSION }));
491
+ }
492
+
493
+ function cmdInstall(root, args) {
494
+ const options = {
495
+ bootstrap: true,
496
+ answers: {},
497
+ interactive: true,
498
+ locale: null,
499
+ bootstrapMode: "auto",
500
+ technicalLevel: null,
501
+ projectState: null,
502
+ docsState: null,
503
+ decisionOwnership: null,
504
+ };
505
+ for (let i = 0; i < (args || []).length; i += 1) {
506
+ if (args[i] === "--locale" && args[i + 1]) {
507
+ options.locale = args[i + 1];
508
+ i += 1;
509
+ } else if (args[i] === "--no-bootstrap") {
510
+ options.bootstrap = false;
511
+ } else if (args[i] === "--non-interactive") {
512
+ options.interactive = false;
513
+ } else if (args[i] === "--bootstrap-mode" && args[i + 1]) {
514
+ options.bootstrapMode = args[i + 1];
515
+ i += 1;
516
+ } else if (args[i] === "--technical-level" && args[i + 1]) {
517
+ options.technicalLevel = args[i + 1];
518
+ i += 1;
519
+ } else if (args[i] === "--project-state" && args[i + 1]) {
520
+ options.projectState = args[i + 1];
521
+ i += 1;
522
+ } else if (args[i] === "--docs-state" && args[i + 1]) {
523
+ options.docsState = args[i + 1];
524
+ i += 1;
525
+ } else if (args[i] === "--decision-ownership" && args[i + 1]) {
526
+ options.decisionOwnership = args[i + 1];
527
+ i += 1;
528
+ }
529
+ }
530
+ return install(root, options);
531
+ }
532
+
533
+ function cmdStatus(root) { status(root); }
534
+ function cmdConfigure(root, args) { configure(root, args); }
535
+ function cmdUpgrade(root, args) { upgrade(root, args); }
536
+
537
+ async function cmdBootstrap(root, args) {
538
+ const options = {
539
+ locale: null,
540
+ interactive: true,
541
+ answers: {},
542
+ resume: false,
543
+ bootstrapMode: "auto",
544
+ technicalLevel: null,
545
+ projectState: null,
546
+ docsState: null,
547
+ decisionOwnership: null,
548
+ };
549
+ for (let i = 0; i < (args || []).length; i += 1) {
550
+ if (args[i] === "--locale" && args[i + 1]) {
551
+ options.locale = args[i + 1];
552
+ i += 1;
553
+ } else if (args[i] === "--non-interactive") {
554
+ options.interactive = false;
555
+ } else if (args[i] === "--resume") {
556
+ options.resume = true;
557
+ } else if (args[i] === "--bootstrap-mode" && args[i + 1]) {
558
+ options.bootstrapMode = args[i + 1];
559
+ i += 1;
560
+ } else if (args[i] === "--technical-level" && args[i + 1]) {
561
+ options.technicalLevel = args[i + 1];
562
+ i += 1;
563
+ } else if (args[i] === "--project-state" && args[i + 1]) {
564
+ options.projectState = args[i + 1];
565
+ i += 1;
566
+ } else if (args[i] === "--docs-state" && args[i + 1]) {
567
+ options.docsState = args[i + 1];
568
+ i += 1;
569
+ } else if (args[i] === "--decision-ownership" && args[i + 1]) {
570
+ options.decisionOwnership = args[i + 1];
408
571
  i += 1;
409
572
  }
410
573
  }
574
+ return runBootstrap(root, options);
575
+ }
576
+
577
+ function cmdHandoff(root, args) {
578
+ const context = config.ensureContext(root);
579
+ const control = config.loadControl(context);
580
+ const state = bootstrap.getBootstrapState(control, context) || bootstrap.detectLegacyBootstrap(context, control);
581
+ if (!state) {
582
+ throw new Error("OPERA bootstrap is not initialized.");
583
+ }
584
+ const files = bootstrap.bootstrapFilePaths(context);
585
+ const printMode = (args || []).includes("--print");
586
+ const jsonMode = (args || []).includes("--json");
587
+ if (jsonMode) {
588
+ const payload = readText(files.json);
589
+ process.stdout.write(payload || "{}\n");
590
+ return;
591
+ }
592
+ if (printMode) {
593
+ process.stdout.write(readText(files.markdown) || "");
594
+ return;
595
+ }
596
+ console.log(`Bootstrap: ${state.status}`);
597
+ console.log(`Mode: ${state.mode}`);
598
+ console.log(`Markdown handoff: ${state.handoffFiles?.markdown || bootstrap.bootstrapRelativePaths(context).markdown}`);
599
+ console.log(`JSON handoff: ${state.handoffFiles?.json || bootstrap.bootstrapRelativePaths(context).json}`);
600
+ if (state.reviewFiles?.openQuestions) {
601
+ console.log(`Open questions: ${state.reviewFiles.openQuestions}`);
602
+ }
603
+ }
411
604
 
412
- config.saveControl(context, control);
413
- if (config.isOperaInstalled(control)) {
414
- installStructure(context, control, nextLocale, { rewriteLocalizedTemplates: true });
415
- env.syncEnvironment(context, control);
416
- const ops = require("./control");
417
- ops.syncDocs(context, control);
418
- }
419
- console.log(t("opera.configure.updated"));
420
- }
421
-
422
- function upgrade(root, args = []) {
423
- const context = config.ensureContext(root);
424
- const control = config.loadControl(context);
425
- const locale = config.getLocale(control);
426
- setLocale(locale);
427
- const wantsStable = (args || []).includes("--stable");
428
- const wantsReset = (args || []).includes("--reset");
429
-
430
- if (!config.isOperaInstalled(control)) {
431
- console.log(t("opera.notInstalled"));
432
- console.log(t("opera.upgrade.runInstallFirst"));
433
- return;
434
- }
435
-
436
- if (!wantsStable) {
437
- console.log(t("opera.upgrade.usage"));
438
- return;
439
- }
440
-
441
- const legacy = bootstrap.detectLegacyBootstrap(context, control);
442
- const isLegacyUnsupported = legacy?.status === "legacy_unsupported";
443
- if (isLegacyUnsupported && !wantsReset) {
444
- control.meta.opera = control.meta.opera || {};
445
- control.meta.opera.legacyStatus = "legacy_unsupported";
446
- config.saveControl(context, control);
447
- console.log(t("opera.upgrade.legacyUnsupported"));
448
- return;
449
- }
450
-
451
- const backup = backupManagedArtifacts(context);
452
- if (wantsReset) {
453
- removePath(context.paths.agentHubDir);
454
- removePath(context.paths.skillsDir);
455
- removePath(context.paths.genesisFile);
456
- removePath(context.paths.contractFile);
457
- removePath(context.paths.autonomyPolicyFile);
458
- removePath(context.paths.bootstrapDir);
459
- }
460
-
461
- installStructure(context, control, locale, { rewriteLocalizedTemplates: true });
462
- control.meta.opera = {
463
- ...(control.meta.opera || {}),
464
- installed: true,
465
- model: "v3",
466
- stableTag: "stable",
467
- version: OPERA_VERSION,
468
- legacyStatus: "supported",
469
- contractVersion: fs.existsSync(context.paths.contractFile) ? bootstrap.CONTRACT_VERSION : null,
470
- contractReadiness: fs.existsSync(context.paths.contractFile)
471
- ? (control.meta?.opera?.contractReadiness || "verified")
472
- : "hypothesis",
473
- bootstrap: wantsReset
474
- ? bootstrap.createAwaitingBootstrapState(context)
475
- : (control.meta?.opera?.bootstrap || bootstrap.createAwaitingBootstrapState(context)),
476
- };
477
- config.saveControl(context, control);
478
- env.syncEnvironment(context, control);
479
- require("./skills").updateRegistry(context);
480
- console.log(t("opera.upgrade.backup", { path: path.relative(context.workspaceRoot, backup.backupRoot) }));
481
- console.log(t("opera.upgraded", { version: OPERA_VERSION }));
482
- }
483
-
484
- function cmdInstall(root, args) {
485
- const options = {
486
- bootstrap: true,
487
- answers: {},
488
- interactive: true,
489
- locale: null,
490
- bootstrapMode: "auto",
491
- technicalLevel: null,
492
- projectState: null,
493
- docsState: null,
494
- decisionOwnership: null,
495
- };
496
- for (let i = 0; i < (args || []).length; i += 1) {
497
- if (args[i] === "--locale" && args[i + 1]) {
498
- options.locale = args[i + 1];
499
- i += 1;
500
- } else if (args[i] === "--no-bootstrap") {
501
- options.bootstrap = false;
502
- } else if (args[i] === "--non-interactive") {
503
- options.interactive = false;
504
- } else if (args[i] === "--bootstrap-mode" && args[i + 1]) {
505
- options.bootstrapMode = args[i + 1];
506
- i += 1;
507
- } else if (args[i] === "--technical-level" && args[i + 1]) {
508
- options.technicalLevel = args[i + 1];
509
- i += 1;
510
- } else if (args[i] === "--project-state" && args[i + 1]) {
511
- options.projectState = args[i + 1];
512
- i += 1;
513
- } else if (args[i] === "--docs-state" && args[i + 1]) {
514
- options.docsState = args[i + 1];
515
- i += 1;
516
- } else if (args[i] === "--decision-ownership" && args[i + 1]) {
517
- options.decisionOwnership = args[i + 1];
518
- i += 1;
519
- }
520
- }
521
- return install(root, options);
522
- }
523
-
524
- function cmdStatus(root) { status(root); }
525
- function cmdConfigure(root, args) { configure(root, args); }
526
- function cmdUpgrade(root, args) { upgrade(root, args); }
527
-
528
- async function cmdBootstrap(root, args) {
529
- const options = {
530
- locale: null,
531
- interactive: true,
532
- answers: {},
533
- resume: false,
534
- bootstrapMode: "auto",
535
- technicalLevel: null,
536
- projectState: null,
537
- docsState: null,
538
- decisionOwnership: null,
539
- };
540
- for (let i = 0; i < (args || []).length; i += 1) {
541
- if (args[i] === "--locale" && args[i + 1]) {
542
- options.locale = args[i + 1];
543
- i += 1;
544
- } else if (args[i] === "--non-interactive") {
545
- options.interactive = false;
546
- } else if (args[i] === "--resume") {
547
- options.resume = true;
548
- } else if (args[i] === "--bootstrap-mode" && args[i + 1]) {
549
- options.bootstrapMode = args[i + 1];
550
- i += 1;
551
- } else if (args[i] === "--technical-level" && args[i + 1]) {
552
- options.technicalLevel = args[i + 1];
553
- i += 1;
554
- } else if (args[i] === "--project-state" && args[i + 1]) {
555
- options.projectState = args[i + 1];
556
- i += 1;
557
- } else if (args[i] === "--docs-state" && args[i + 1]) {
558
- options.docsState = args[i + 1];
559
- i += 1;
560
- } else if (args[i] === "--decision-ownership" && args[i + 1]) {
561
- options.decisionOwnership = args[i + 1];
562
- i += 1;
563
- }
564
- }
565
- return runBootstrap(root, options);
566
- }
567
-
568
- function cmdHandoff(root, args) {
569
- const context = config.ensureContext(root);
570
- const control = config.loadControl(context);
571
- const state = bootstrap.getBootstrapState(control, context) || bootstrap.detectLegacyBootstrap(context, control);
572
- if (!state) {
573
- throw new Error("OPERA bootstrap is not initialized.");
574
- }
575
- const files = bootstrap.bootstrapFilePaths(context);
576
- const printMode = (args || []).includes("--print");
577
- const jsonMode = (args || []).includes("--json");
578
- if (jsonMode) {
579
- const payload = readText(files.json);
580
- process.stdout.write(payload || "{}\n");
581
- return;
582
- }
583
- if (printMode) {
584
- process.stdout.write(readText(files.markdown) || "");
585
- return;
586
- }
587
- console.log(`Bootstrap: ${state.status}`);
588
- console.log(`Mode: ${state.mode}`);
589
- console.log(`Markdown handoff: ${state.handoffFiles?.markdown || bootstrap.bootstrapRelativePaths(context).markdown}`);
590
- console.log(`JSON handoff: ${state.handoffFiles?.json || bootstrap.bootstrapRelativePaths(context).json}`);
591
- if (state.reviewFiles?.openQuestions) {
592
- console.log(`Open questions: ${state.reviewFiles.openQuestions}`);
593
- }
594
- }
595
-
596
- module.exports = {
597
- installStructure,
598
- install,
599
- runBootstrap,
605
+ module.exports = {
606
+ installStructure,
607
+ install,
608
+ runBootstrap,
600
609
  status,
601
610
  configure,
602
611
  upgrade,
603
612
  cmdInstall,
604
613
  cmdStatus,
605
- cmdConfigure,
606
- cmdUpgrade,
607
- cmdBootstrap,
608
- cmdHandoff,
609
- };
614
+ cmdConfigure,
615
+ cmdUpgrade,
616
+ cmdBootstrap,
617
+ cmdHandoff,
618
+ };