trackops 1.1.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -230
- package/bin/trackops.js +54 -28
- package/lib/config.js +14 -10
- package/lib/control.js +44 -32
- package/lib/env.js +18 -1
- package/lib/init.js +40 -6
- package/lib/opera-bootstrap.js +825 -273
- package/lib/opera.js +360 -110
- package/lib/preferences.js +74 -0
- package/lib/runtime-state.js +144 -0
- package/lib/server.js +155 -25
- package/locales/en.json +136 -42
- package/locales/es.json +136 -42
- package/package.json +2 -1
- package/scripts/postinstall-locale.js +21 -0
- package/scripts/smoke-tests.js +130 -5
- package/scripts/validate-skill.js +2 -1
- package/skills/trackops/SKILL.md +67 -45
- package/skills/trackops/agents/openai.yaml +5 -1
- package/skills/trackops/locales/en/SKILL.md +86 -0
- package/skills/trackops/locales/en/references/activation.md +73 -0
- package/skills/trackops/locales/en/references/troubleshooting.md +49 -0
- package/skills/trackops/locales/en/references/workflow.md +26 -0
- package/skills/trackops/references/activation.md +53 -19
- package/skills/trackops/references/troubleshooting.md +36 -21
- package/skills/trackops/references/workflow.md +21 -15
- package/skills/trackops/scripts/bootstrap-trackops.js +9 -7
- package/skills/trackops/skill.json +4 -4
- package/templates/opera/agent.md +10 -9
- package/templates/opera/architecture/dependency-graph.md +24 -0
- package/templates/opera/architecture/runtime-automation.md +24 -0
- package/templates/opera/architecture/runtime-operations.md +34 -0
- package/templates/opera/en/agent.md +21 -20
- package/templates/opera/en/architecture/dependency-graph.md +24 -0
- package/templates/opera/en/architecture/runtime-automation.md +24 -0
- package/templates/opera/en/architecture/runtime-operations.md +34 -0
- package/templates/opera/en/reviews/delivery-audit.md +18 -0
- package/templates/opera/en/reviews/integration-audit.md +18 -0
- package/templates/opera/en/router.md +19 -9
- package/templates/opera/reviews/delivery-audit.md +18 -0
- package/templates/opera/reviews/integration-audit.md +18 -0
- package/templates/opera/router.md +15 -5
- package/templates/skills/opera-contract-auditor/SKILL.md +38 -0
- package/templates/skills/opera-contract-auditor/locales/en/SKILL.md +38 -0
- package/templates/skills/opera-policy-guard/SKILL.md +26 -0
- package/templates/skills/opera-policy-guard/locales/en/SKILL.md +26 -0
- package/templates/skills/project-starter-skill/SKILL.md +89 -164
- package/templates/skills/project-starter-skill/locales/en/SKILL.md +104 -24
- package/ui/js/views/overview.js +16 -12
- package/templates/etapa/agent.md +0 -26
- package/templates/etapa/genesis.md +0 -94
- package/templates/etapa/references/autonomy-and-recovery.md +0 -117
- package/templates/etapa/references/etapa-cycle.md +0 -193
- package/templates/etapa/registry.md +0 -28
- package/templates/etapa/router.md +0 -39
package/lib/opera.js
CHANGED
|
@@ -9,14 +9,20 @@ const { t, setLocale } = require("./i18n");
|
|
|
9
9
|
const { promptForLocale, resolveLocale } = require("./locale");
|
|
10
10
|
const { resolveLocalizedFile, resolveLocalizedDir, resolveSkillFile } = require("./resources");
|
|
11
11
|
const bootstrap = require("./opera-bootstrap");
|
|
12
|
+
const runtimeState = require("./runtime-state");
|
|
12
13
|
|
|
13
14
|
const TEMPLATES_DIR = path.join(__dirname, "..", "templates", "opera");
|
|
14
15
|
const SKILLS_TEMPLATES_DIR = path.join(__dirname, "..", "templates", "skills");
|
|
15
16
|
const OPERA_VERSION = require("../package.json").version;
|
|
17
|
+
const AUXILIARY_SKILLS = ["project-starter-skill", "opera-contract-auditor", "opera-policy-guard"];
|
|
16
18
|
|
|
17
|
-
function nowIso() {
|
|
18
|
-
return new Date().toISOString();
|
|
19
|
-
}
|
|
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
|
+
}
|
|
20
26
|
|
|
21
27
|
function readText(filePath) {
|
|
22
28
|
return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
|
|
@@ -47,23 +53,38 @@ function copyTemplate(templatePath, targetPath, replacements, options = {}) {
|
|
|
47
53
|
return true;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
|
-
function copyDirRecursive(src, dest) {
|
|
51
|
-
if (!src || !fs.existsSync(src)) return;
|
|
52
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
53
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
54
|
-
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);
|
|
55
61
|
const destPath = path.join(dest, entry.name);
|
|
56
62
|
if (entry.isDirectory()) {
|
|
57
63
|
copyDirRecursive(srcPath, destPath);
|
|
58
64
|
} else {
|
|
59
65
|
fs.copyFileSync(srcPath, destPath);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
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
|
+
}
|
|
63
84
|
|
|
64
|
-
function resolveOperaLocale(control, options = {}) {
|
|
65
|
-
return resolveLocale(options.locale, config.getLocale(control));
|
|
66
|
-
}
|
|
85
|
+
function resolveOperaLocale(control, options = {}) {
|
|
86
|
+
return resolveLocale(options.locale, config.getLocale(control) || runtimeState.getGlobalLocale());
|
|
87
|
+
}
|
|
67
88
|
|
|
68
89
|
function installStructure(root, control, locale, options = {}) {
|
|
69
90
|
const context = config.ensureContext(root);
|
|
@@ -89,6 +110,11 @@ function installStructure(root, control, locale, options = {}) {
|
|
|
89
110
|
|
|
90
111
|
const agentHubDir = context.paths.agentHubDir;
|
|
91
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 });
|
|
92
118
|
|
|
93
119
|
copyTemplate(
|
|
94
120
|
resolveLocalizedFile(TEMPLATES_DIR, locale, "agent.md"),
|
|
@@ -118,20 +144,36 @@ function installStructure(root, control, locale, options = {}) {
|
|
|
118
144
|
copyTemplate(genesisTemplatePath, genesisPath, replacements, { overwrite: true });
|
|
119
145
|
}
|
|
120
146
|
|
|
121
|
-
const refsTemplateDir = resolveLocalizedDir(TEMPLATES_DIR, locale, "references");
|
|
147
|
+
const refsTemplateDir = resolveLocalizedDir(TEMPLATES_DIR, locale, "references");
|
|
122
148
|
const refsTargetDir = path.join(context.paths.skillsDir, "project-starter-skill", "references");
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
+
|
|
135
177
|
async function install(root, options = {}) {
|
|
136
178
|
const context = config.ensureContext(root);
|
|
137
179
|
const controlFile = config.controlFilePath(context);
|
|
@@ -141,9 +183,12 @@ async function install(root, options = {}) {
|
|
|
141
183
|
|
|
142
184
|
const control = config.loadControl(context);
|
|
143
185
|
let locale = resolveOperaLocale(control, options);
|
|
144
|
-
if (!options.locale && !control.meta?.locale) {
|
|
145
|
-
locale = await promptForLocale(locale);
|
|
146
|
-
|
|
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
|
+
}
|
|
147
192
|
control.meta.locale = locale;
|
|
148
193
|
setLocale(locale);
|
|
149
194
|
|
|
@@ -151,12 +196,18 @@ async function install(root, options = {}) {
|
|
|
151
196
|
installStructure(context, control, locale);
|
|
152
197
|
|
|
153
198
|
control.meta.opera = {
|
|
154
|
-
...(control.meta.opera || {}),
|
|
155
|
-
installed: true,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
199
|
+
...(control.meta.opera || {}),
|
|
200
|
+
installed: true,
|
|
201
|
+
model: "v3",
|
|
202
|
+
stableTag: "stable",
|
|
203
|
+
version: OPERA_VERSION,
|
|
204
|
+
installedAt: control.meta?.opera?.installedAt || nowIso(),
|
|
205
|
+
skills: control.meta?.opera?.skills || [],
|
|
206
|
+
legacyStatus: "supported",
|
|
207
|
+
};
|
|
208
|
+
if (!control.meta.opera.bootstrap && options.bootstrap === false) {
|
|
209
|
+
control.meta.opera.bootstrap = bootstrap.createAwaitingBootstrapState(context);
|
|
210
|
+
}
|
|
160
211
|
config.saveControl(context, control);
|
|
161
212
|
env.syncEnvironment(context, control);
|
|
162
213
|
|
|
@@ -174,10 +225,53 @@ async function install(root, options = {}) {
|
|
|
174
225
|
// ignore
|
|
175
226
|
}
|
|
176
227
|
}
|
|
228
|
+
skills.updateRegistry(context);
|
|
177
229
|
|
|
178
230
|
if (options.bootstrap !== false) {
|
|
179
|
-
await runBootstrap(context, {
|
|
231
|
+
await runBootstrap(context, {
|
|
232
|
+
locale,
|
|
233
|
+
answers: options.answers,
|
|
234
|
+
interactive: options.interactive,
|
|
235
|
+
bootstrapMode: options.bootstrapMode,
|
|
236
|
+
technicalLevel: options.technicalLevel,
|
|
237
|
+
projectState: options.projectState,
|
|
238
|
+
docsState: options.docsState,
|
|
239
|
+
decisionOwnership: options.decisionOwnership,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function removePath(targetPath) {
|
|
245
|
+
if (!targetPath || !fs.existsSync(targetPath)) return;
|
|
246
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function backupManagedArtifacts(context) {
|
|
250
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
251
|
+
const backupRoot = path.join(context.paths.tmpDir, "upgrade-backups", timestamp);
|
|
252
|
+
const items = [
|
|
253
|
+
context.paths.agentHubDir,
|
|
254
|
+
context.paths.skillsDir,
|
|
255
|
+
context.paths.genesisFile,
|
|
256
|
+
context.paths.contractFile,
|
|
257
|
+
context.paths.autonomyPolicyFile,
|
|
258
|
+
context.paths.bootstrapDir,
|
|
259
|
+
];
|
|
260
|
+
fs.mkdirSync(backupRoot, { recursive: true });
|
|
261
|
+
let copied = 0;
|
|
262
|
+
for (const item of items) {
|
|
263
|
+
if (!fs.existsSync(item)) continue;
|
|
264
|
+
const relative = path.relative(context.workspaceRoot, item);
|
|
265
|
+
const destination = path.join(backupRoot, relative);
|
|
266
|
+
if (fs.statSync(item).isDirectory()) {
|
|
267
|
+
copyDirRecursive(item, destination);
|
|
268
|
+
} else {
|
|
269
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
270
|
+
fs.copyFileSync(item, destination);
|
|
271
|
+
}
|
|
272
|
+
copied += 1;
|
|
180
273
|
}
|
|
274
|
+
return { backupRoot, copied };
|
|
181
275
|
}
|
|
182
276
|
|
|
183
277
|
async function runBootstrap(root, options = {}) {
|
|
@@ -194,17 +288,37 @@ async function runBootstrap(root, options = {}) {
|
|
|
194
288
|
config.saveControl(context, control);
|
|
195
289
|
}
|
|
196
290
|
|
|
291
|
+
if ((options.resume || options.forceResume) && control.meta?.opera?.bootstrap) {
|
|
292
|
+
const resumed = bootstrap.resumeBootstrap(context, control);
|
|
293
|
+
if (resumed.resumed) {
|
|
294
|
+
const updatedControl = bootstrap.applyBootstrap(context, control, resumed.profile);
|
|
295
|
+
env.syncEnvironment(context, updatedControl, { requiredKeys: env.inferRequiredKeys(updatedControl, context) });
|
|
296
|
+
const ops = require("./control");
|
|
297
|
+
ops.syncDocs(context, updatedControl);
|
|
298
|
+
ops.refreshRepoRuntime(context, { quiet: true });
|
|
299
|
+
console.log(t("bootstrap.completed"));
|
|
300
|
+
return resumed.profile;
|
|
301
|
+
}
|
|
302
|
+
console.log(t("bootstrap.awaitingAgent"));
|
|
303
|
+
return control.meta.opera.bootstrap;
|
|
304
|
+
}
|
|
305
|
+
|
|
197
306
|
const profile = await bootstrap.collectBootstrapProfile(context, control, options);
|
|
198
307
|
const updatedControl = bootstrap.applyBootstrap(context, control, profile);
|
|
199
308
|
env.syncEnvironment(context, updatedControl, { requiredKeys: env.inferRequiredKeys(updatedControl, context) });
|
|
200
309
|
const ops = require("./control");
|
|
201
310
|
ops.syncDocs(context, updatedControl);
|
|
202
311
|
ops.refreshRepoRuntime(context, { quiet: true });
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
312
|
+
|
|
313
|
+
if (profile.mode === "agent_handoff") {
|
|
314
|
+
console.log(t("bootstrap.awaitingAgent"));
|
|
315
|
+
console.log(`${t("bootstrap.handoffFile")}: ${profile.handoffFiles?.markdown || bootstrap.bootstrapRelativePaths(context).markdown}`);
|
|
316
|
+
} else {
|
|
317
|
+
console.log(profile.status === "completed" ? t("bootstrap.completed") : t("bootstrap.pending"));
|
|
318
|
+
}
|
|
319
|
+
return profile;
|
|
320
|
+
}
|
|
321
|
+
|
|
208
322
|
function status(root) {
|
|
209
323
|
const context = config.ensureContext(root);
|
|
210
324
|
const control = config.loadControl(context);
|
|
@@ -214,30 +328,51 @@ function status(root) {
|
|
|
214
328
|
console.log(t("opera.notInstalled"));
|
|
215
329
|
return;
|
|
216
330
|
}
|
|
217
|
-
|
|
218
|
-
const opera = control.meta.opera;
|
|
331
|
+
|
|
332
|
+
const opera = control.meta.opera;
|
|
219
333
|
const bootstrapState = opera.bootstrap || bootstrap.detectLegacyBootstrap(context, control);
|
|
220
|
-
|
|
221
|
-
console.log(
|
|
222
|
-
console.log(
|
|
223
|
-
console.log(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
334
|
+
const localeDoctor = runtimeState.doctorLocale(control.meta?.locale || null);
|
|
335
|
+
console.log(t("opera.status.version", { version: opera.version }));
|
|
336
|
+
console.log(t("opera.status.installed", { value: opera.installedAt }));
|
|
337
|
+
console.log(t("opera.status.skills", { value: (opera.skills || []).join(", ") || t("locale.none") }));
|
|
338
|
+
console.log(t("opera.status.locale", { locale: config.getLocale(control), source: formatLocaleSource(localeDoctor.source) }));
|
|
339
|
+
console.log(t("opera.status.legacy", { value: opera.legacyStatus || bootstrapState?.status || "supported" }));
|
|
340
|
+
console.log(t("opera.status.contractVersion", { value: opera.contractVersion || t("locale.none") }));
|
|
341
|
+
console.log(t("opera.status.contractReadiness", { value: opera.contractReadiness || "hypothesis" }));
|
|
342
|
+
|
|
343
|
+
if (bootstrapState) {
|
|
344
|
+
console.log(t("opera.status.bootstrap", { value: bootstrapState.status }));
|
|
345
|
+
if (bootstrapState.mode) {
|
|
346
|
+
console.log(t("opera.status.mode", { value: bootstrapState.mode }));
|
|
347
|
+
}
|
|
348
|
+
if (bootstrapState.routeReason) {
|
|
349
|
+
console.log(t("opera.status.route", { value: bootstrapState.routeReason }));
|
|
350
|
+
}
|
|
351
|
+
if (bootstrapState.decisionOwnership) {
|
|
352
|
+
console.log(t("opera.status.ownership", { value: bootstrapState.decisionOwnership }));
|
|
353
|
+
}
|
|
354
|
+
if ((bootstrapState.missingFields || []).length) {
|
|
355
|
+
console.log(t("opera.status.missing", { value: bootstrapState.missingFields.join(", ") }));
|
|
356
|
+
console.log(t("opera.status.resume"));
|
|
357
|
+
}
|
|
358
|
+
if (bootstrapState.handoffFiles?.markdown) {
|
|
359
|
+
console.log(t("opera.status.handoff", { value: bootstrapState.handoffFiles.markdown }));
|
|
360
|
+
}
|
|
361
|
+
if (bootstrapState.reviewFiles?.qualityReport) {
|
|
362
|
+
console.log(t("opera.status.qualityReport", { value: bootstrapState.reviewFiles.qualityReport }));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
233
366
|
const checks = [
|
|
234
|
-
[".agent/hub/agent.md", fs.existsSync(path.join(context.paths.agentHubDir, "agent.md"))],
|
|
235
|
-
[".agent/hub/router.md", fs.existsSync(path.join(context.paths.agentHubDir, "router.md"))],
|
|
236
|
-
[".agents/skills/_registry.md", fs.existsSync(context.paths.registryPath)],
|
|
237
|
-
["genesis.md", fs.existsSync(context.paths.genesisFile)],
|
|
367
|
+
[context.layout === "split" ? "ops/.agent/hub/agent.md" : ".agent/hub/agent.md", fs.existsSync(path.join(context.paths.agentHubDir, "agent.md"))],
|
|
368
|
+
[context.layout === "split" ? "ops/.agent/hub/router.md" : ".agent/hub/router.md", fs.existsSync(path.join(context.paths.agentHubDir, "router.md"))],
|
|
369
|
+
[context.layout === "split" ? "ops/.agents/skills/_registry.md" : ".agents/skills/_registry.md", fs.existsSync(context.paths.registryPath)],
|
|
370
|
+
[context.layout === "split" ? "ops/genesis.md" : "genesis.md", fs.existsSync(context.paths.genesisFile)],
|
|
371
|
+
[context.layout === "split" ? "ops/contract/operating-contract.json" : "contract/operating-contract.json", fs.existsSync(context.paths.contractFile)],
|
|
372
|
+
[context.layout === "split" ? "ops/policy/autonomy.json" : "policy/autonomy.json", fs.existsSync(context.paths.autonomyPolicyFile)],
|
|
238
373
|
];
|
|
239
374
|
|
|
240
|
-
console.log("
|
|
375
|
+
console.log(t("opera.status.structure"));
|
|
241
376
|
for (const [file, exists] of checks) {
|
|
242
377
|
console.log(` ${exists ? "\u2705" : "\u274C"} ${file}`);
|
|
243
378
|
}
|
|
@@ -255,13 +390,13 @@ function configure(root, args) {
|
|
|
255
390
|
control.meta.locale = nextLocale;
|
|
256
391
|
i += 1;
|
|
257
392
|
}
|
|
258
|
-
if (args[i] === "--phases" && args[i + 1]) {
|
|
259
|
-
try {
|
|
260
|
-
control.meta.phases = JSON.parse(args[i + 1]);
|
|
261
|
-
} catch (_e) {
|
|
262
|
-
console.error("
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
393
|
+
if (args[i] === "--phases" && args[i + 1]) {
|
|
394
|
+
try {
|
|
395
|
+
control.meta.phases = JSON.parse(args[i + 1]);
|
|
396
|
+
} catch (_e) {
|
|
397
|
+
console.error(t("opera.configure.invalidPhases"));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
265
400
|
i += 1;
|
|
266
401
|
}
|
|
267
402
|
}
|
|
@@ -273,34 +408,82 @@ function configure(root, args) {
|
|
|
273
408
|
const ops = require("./control");
|
|
274
409
|
ops.syncDocs(context, control);
|
|
275
410
|
}
|
|
276
|
-
console.log("
|
|
411
|
+
console.log(t("opera.configure.updated"));
|
|
277
412
|
}
|
|
278
413
|
|
|
279
|
-
function upgrade(root) {
|
|
414
|
+
function upgrade(root, args = []) {
|
|
280
415
|
const context = config.ensureContext(root);
|
|
281
416
|
const control = config.loadControl(context);
|
|
282
417
|
const locale = config.getLocale(control);
|
|
283
|
-
setLocale(locale);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
418
|
+
setLocale(locale);
|
|
419
|
+
const wantsStable = (args || []).includes("--stable");
|
|
420
|
+
const wantsReset = (args || []).includes("--reset");
|
|
421
|
+
|
|
422
|
+
if (!config.isOperaInstalled(control)) {
|
|
423
|
+
console.log(t("opera.notInstalled"));
|
|
424
|
+
console.log(t("opera.upgrade.runInstallFirst"));
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (!wantsStable) {
|
|
429
|
+
console.log(t("opera.upgrade.usage"));
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const legacy = bootstrap.detectLegacyBootstrap(context, control);
|
|
434
|
+
const isLegacyUnsupported = legacy?.status === "legacy_unsupported";
|
|
435
|
+
if (isLegacyUnsupported && !wantsReset) {
|
|
436
|
+
control.meta.opera = control.meta.opera || {};
|
|
437
|
+
control.meta.opera.legacyStatus = "legacy_unsupported";
|
|
438
|
+
config.saveControl(context, control);
|
|
439
|
+
console.log(t("opera.upgrade.legacyUnsupported"));
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const backup = backupManagedArtifacts(context);
|
|
444
|
+
if (wantsReset) {
|
|
445
|
+
removePath(context.paths.agentHubDir);
|
|
446
|
+
removePath(context.paths.skillsDir);
|
|
447
|
+
removePath(context.paths.genesisFile);
|
|
448
|
+
removePath(context.paths.contractFile);
|
|
449
|
+
removePath(context.paths.autonomyPolicyFile);
|
|
450
|
+
removePath(context.paths.bootstrapDir);
|
|
451
|
+
}
|
|
452
|
+
|
|
291
453
|
installStructure(context, control, locale, { rewriteLocalizedTemplates: true });
|
|
292
|
-
control.meta.opera
|
|
454
|
+
control.meta.opera = {
|
|
455
|
+
...(control.meta.opera || {}),
|
|
456
|
+
installed: true,
|
|
457
|
+
model: "v3",
|
|
458
|
+
stableTag: "stable",
|
|
459
|
+
version: OPERA_VERSION,
|
|
460
|
+
legacyStatus: "supported",
|
|
461
|
+
contractVersion: fs.existsSync(context.paths.contractFile) ? bootstrap.CONTRACT_VERSION : null,
|
|
462
|
+
contractReadiness: fs.existsSync(context.paths.contractFile)
|
|
463
|
+
? (control.meta?.opera?.contractReadiness || "verified")
|
|
464
|
+
: "hypothesis",
|
|
465
|
+
bootstrap: wantsReset
|
|
466
|
+
? bootstrap.createAwaitingBootstrapState(context)
|
|
467
|
+
: (control.meta?.opera?.bootstrap || bootstrap.createAwaitingBootstrapState(context)),
|
|
468
|
+
};
|
|
293
469
|
config.saveControl(context, control);
|
|
294
470
|
env.syncEnvironment(context, control);
|
|
471
|
+
require("./skills").updateRegistry(context);
|
|
472
|
+
console.log(t("opera.upgrade.backup", { path: path.relative(context.workspaceRoot, backup.backupRoot) }));
|
|
295
473
|
console.log(t("opera.upgraded", { version: OPERA_VERSION }));
|
|
296
474
|
}
|
|
297
|
-
|
|
475
|
+
|
|
298
476
|
function cmdInstall(root, args) {
|
|
299
477
|
const options = {
|
|
300
478
|
bootstrap: true,
|
|
301
479
|
answers: {},
|
|
302
480
|
interactive: true,
|
|
303
481
|
locale: null,
|
|
482
|
+
bootstrapMode: "auto",
|
|
483
|
+
technicalLevel: null,
|
|
484
|
+
projectState: null,
|
|
485
|
+
docsState: null,
|
|
486
|
+
decisionOwnership: null,
|
|
304
487
|
};
|
|
305
488
|
for (let i = 0; i < (args || []).length; i += 1) {
|
|
306
489
|
if (args[i] === "--locale" && args[i + 1]) {
|
|
@@ -310,42 +493,109 @@ function cmdInstall(root, args) {
|
|
|
310
493
|
options.bootstrap = false;
|
|
311
494
|
} else if (args[i] === "--non-interactive") {
|
|
312
495
|
options.interactive = false;
|
|
496
|
+
} else if (args[i] === "--bootstrap-mode" && args[i + 1]) {
|
|
497
|
+
options.bootstrapMode = args[i + 1];
|
|
498
|
+
i += 1;
|
|
499
|
+
} else if (args[i] === "--technical-level" && args[i + 1]) {
|
|
500
|
+
options.technicalLevel = args[i + 1];
|
|
501
|
+
i += 1;
|
|
502
|
+
} else if (args[i] === "--project-state" && args[i + 1]) {
|
|
503
|
+
options.projectState = args[i + 1];
|
|
504
|
+
i += 1;
|
|
505
|
+
} else if (args[i] === "--docs-state" && args[i + 1]) {
|
|
506
|
+
options.docsState = args[i + 1];
|
|
507
|
+
i += 1;
|
|
508
|
+
} else if (args[i] === "--decision-ownership" && args[i + 1]) {
|
|
509
|
+
options.decisionOwnership = args[i + 1];
|
|
510
|
+
i += 1;
|
|
313
511
|
}
|
|
314
512
|
}
|
|
315
513
|
return install(root, options);
|
|
316
514
|
}
|
|
515
|
+
|
|
516
|
+
function cmdStatus(root) { status(root); }
|
|
517
|
+
function cmdConfigure(root, args) { configure(root, args); }
|
|
518
|
+
function cmdUpgrade(root, args) { upgrade(root, args); }
|
|
317
519
|
|
|
318
|
-
function
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
520
|
+
async function cmdBootstrap(root, args) {
|
|
521
|
+
const options = {
|
|
522
|
+
locale: null,
|
|
523
|
+
interactive: true,
|
|
524
|
+
answers: {},
|
|
525
|
+
resume: false,
|
|
526
|
+
bootstrapMode: "auto",
|
|
527
|
+
technicalLevel: null,
|
|
528
|
+
projectState: null,
|
|
529
|
+
docsState: null,
|
|
530
|
+
decisionOwnership: null,
|
|
531
|
+
};
|
|
532
|
+
for (let i = 0; i < (args || []).length; i += 1) {
|
|
533
|
+
if (args[i] === "--locale" && args[i + 1]) {
|
|
534
|
+
options.locale = args[i + 1];
|
|
535
|
+
i += 1;
|
|
536
|
+
} else if (args[i] === "--non-interactive") {
|
|
537
|
+
options.interactive = false;
|
|
538
|
+
} else if (args[i] === "--resume") {
|
|
539
|
+
options.resume = true;
|
|
540
|
+
} else if (args[i] === "--bootstrap-mode" && args[i + 1]) {
|
|
541
|
+
options.bootstrapMode = args[i + 1];
|
|
542
|
+
i += 1;
|
|
543
|
+
} else if (args[i] === "--technical-level" && args[i + 1]) {
|
|
544
|
+
options.technicalLevel = args[i + 1];
|
|
545
|
+
i += 1;
|
|
546
|
+
} else if (args[i] === "--project-state" && args[i + 1]) {
|
|
547
|
+
options.projectState = args[i + 1];
|
|
548
|
+
i += 1;
|
|
549
|
+
} else if (args[i] === "--docs-state" && args[i + 1]) {
|
|
550
|
+
options.docsState = args[i + 1];
|
|
551
|
+
i += 1;
|
|
552
|
+
} else if (args[i] === "--decision-ownership" && args[i + 1]) {
|
|
553
|
+
options.decisionOwnership = args[i + 1];
|
|
554
|
+
i += 1;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return runBootstrap(root, options);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function cmdHandoff(root, args) {
|
|
561
|
+
const context = config.ensureContext(root);
|
|
562
|
+
const control = config.loadControl(context);
|
|
563
|
+
const state = bootstrap.getBootstrapState(control, context) || bootstrap.detectLegacyBootstrap(context, control);
|
|
564
|
+
if (!state) {
|
|
565
|
+
throw new Error("OPERA bootstrap is not initialized.");
|
|
566
|
+
}
|
|
567
|
+
const files = bootstrap.bootstrapFilePaths(context);
|
|
568
|
+
const printMode = (args || []).includes("--print");
|
|
569
|
+
const jsonMode = (args || []).includes("--json");
|
|
570
|
+
if (jsonMode) {
|
|
571
|
+
const payload = readText(files.json);
|
|
572
|
+
process.stdout.write(payload || "{}\n");
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (printMode) {
|
|
576
|
+
process.stdout.write(readText(files.markdown) || "");
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
console.log(`Bootstrap: ${state.status}`);
|
|
580
|
+
console.log(`Mode: ${state.mode}`);
|
|
581
|
+
console.log(`Markdown handoff: ${state.handoffFiles?.markdown || bootstrap.bootstrapRelativePaths(context).markdown}`);
|
|
582
|
+
console.log(`JSON handoff: ${state.handoffFiles?.json || bootstrap.bootstrapRelativePaths(context).json}`);
|
|
583
|
+
if (state.reviewFiles?.openQuestions) {
|
|
584
|
+
console.log(`Open questions: ${state.reviewFiles.openQuestions}`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
module.exports = {
|
|
589
|
+
installStructure,
|
|
590
|
+
install,
|
|
591
|
+
runBootstrap,
|
|
343
592
|
status,
|
|
344
593
|
configure,
|
|
345
594
|
upgrade,
|
|
346
595
|
cmdInstall,
|
|
347
596
|
cmdStatus,
|
|
348
|
-
cmdConfigure,
|
|
349
|
-
cmdUpgrade,
|
|
350
|
-
cmdBootstrap,
|
|
351
|
-
|
|
597
|
+
cmdConfigure,
|
|
598
|
+
cmdUpgrade,
|
|
599
|
+
cmdBootstrap,
|
|
600
|
+
cmdHandoff,
|
|
601
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const config = require("./config");
|
|
4
|
+
const runtimeState = require("./runtime-state");
|
|
5
|
+
const { setLocale, t } = require("./i18n");
|
|
6
|
+
const { normalizeLocale } = require("./locale");
|
|
7
|
+
|
|
8
|
+
function formatLocaleSource(source) {
|
|
9
|
+
return t(`locale.source.${String(source || "").trim()}`) || source || t("locale.none");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function resolveProjectLocale(root) {
|
|
13
|
+
const context = config.resolveWorkspaceContext(root || process.cwd());
|
|
14
|
+
if (!context) return null;
|
|
15
|
+
try {
|
|
16
|
+
return config.getLocale(config.loadControl(context));
|
|
17
|
+
} catch (_error) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function cmdLocale(args = [], root) {
|
|
23
|
+
const sub = args[0];
|
|
24
|
+
const projectLocale = resolveProjectLocale(root);
|
|
25
|
+
const doctor = runtimeState.doctorLocale(projectLocale);
|
|
26
|
+
setLocale(doctor.effectiveLocale);
|
|
27
|
+
|
|
28
|
+
if (sub === "get" || !sub) {
|
|
29
|
+
console.log(`${t("locale.effective")}: ${doctor.effectiveLocale}`);
|
|
30
|
+
console.log(`${t("locale.source")}: ${formatLocaleSource(doctor.source)}`);
|
|
31
|
+
console.log(`${t("locale.global")}: ${doctor.globalLocale || t("locale.none")}`);
|
|
32
|
+
if (doctor.projectLocale) {
|
|
33
|
+
console.log(`${t("locale.project")}: ${doctor.projectLocale}`);
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (sub === "set") {
|
|
39
|
+
const nextLocale = normalizeLocale(args[1]);
|
|
40
|
+
if (!nextLocale) {
|
|
41
|
+
throw new Error(t("locale.invalid", { value: String(args[1] || "") }));
|
|
42
|
+
}
|
|
43
|
+
runtimeState.writeRuntimeState({ locale: nextLocale, localeSource: "manual" });
|
|
44
|
+
setLocale(nextLocale);
|
|
45
|
+
console.log(t("locale.updated", { locale: nextLocale }));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(t("cli.usage.locale"));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function cmdDoctor(args = [], root) {
|
|
53
|
+
const sub = args[0];
|
|
54
|
+
if (sub !== "locale") {
|
|
55
|
+
console.log(t("cli.usage.doctor"));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const projectLocale = resolveProjectLocale(root);
|
|
60
|
+
const doctor = runtimeState.doctorLocale(projectLocale);
|
|
61
|
+
setLocale(doctor.effectiveLocale);
|
|
62
|
+
console.log(`${t("locale.effective")}: ${doctor.effectiveLocale}`);
|
|
63
|
+
console.log(`${t("locale.source")}: ${formatLocaleSource(doctor.source)}`);
|
|
64
|
+
console.log(`${t("locale.global")}: ${doctor.globalLocale || t("locale.none")}`);
|
|
65
|
+
console.log(`${t("locale.project")}: ${doctor.projectLocale || t("locale.none")}`);
|
|
66
|
+
console.log(`${t("locale.env")}: ${doctor.envLocale || t("locale.none")}`);
|
|
67
|
+
console.log(`${t("locale.system")}: ${doctor.systemLocale}`);
|
|
68
|
+
console.log(`${t("locale.runtimeFile")}: ${doctor.runtimeFile}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
cmdLocale,
|
|
73
|
+
cmdDoctor,
|
|
74
|
+
};
|