trackops 2.0.5 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -61
- package/bin/trackops.js +28 -7
- package/lib/cli-format.js +118 -0
- package/lib/config.js +29 -3
- package/lib/control.js +278 -116
- package/lib/env.js +40 -28
- package/lib/i18n.js +5 -4
- package/lib/init.js +149 -41
- package/lib/opera-bootstrap.js +251 -71
- package/lib/opera.js +235 -73
- package/lib/skills.js +43 -35
- package/lib/workspace.js +32 -21
- package/locales/en.json +183 -61
- package/locales/es.json +184 -62
- package/package.json +1 -1
- package/scripts/smoke-tests.js +81 -39
- package/skills/trackops/skill.json +2 -2
package/lib/opera.js
CHANGED
|
@@ -10,6 +10,7 @@ const { promptForLocale, maybePromptForLocale, resolveLocale } = require("./loca
|
|
|
10
10
|
const { resolveLocalizedFile, resolveLocalizedDir, resolveSkillFile } = require("./resources");
|
|
11
11
|
const bootstrap = require("./opera-bootstrap");
|
|
12
12
|
const runtimeState = require("./runtime-state");
|
|
13
|
+
const fmt = require("./cli-format");
|
|
13
14
|
|
|
14
15
|
const TEMPLATES_DIR = path.join(__dirname, "..", "templates", "opera");
|
|
15
16
|
const SKILLS_TEMPLATES_DIR = path.join(__dirname, "..", "templates", "skills");
|
|
@@ -20,9 +21,41 @@ function nowIso() {
|
|
|
20
21
|
return new Date().toISOString();
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
function formatLocaleSource(source) {
|
|
24
|
-
return t(`locale.source.${String(source || "").trim()}`) || source || t("locale.none");
|
|
25
|
-
}
|
|
24
|
+
function formatLocaleSource(source) {
|
|
25
|
+
return t(`locale.source.${String(source || "").trim()}`) || source || t("locale.none");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function formatBootstrapStatus(status) {
|
|
29
|
+
return t(`bootstrap.status.${String(status || "").trim()}`) || status || t("locale.none");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatBootstrapMode(mode) {
|
|
33
|
+
return t(`bootstrap.mode.${String(mode || "").trim()}`) || mode || t("locale.none");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function formatBootstrapReason(reason) {
|
|
37
|
+
return t(`bootstrap.reason.${String(reason || "").trim()}`) || reason || t("locale.none");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function formatDecisionOwnership(value) {
|
|
41
|
+
return t(`bootstrap.ownership.${String(value || "").trim()}`) || value || t("locale.none");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function formatContractReadiness(value) {
|
|
45
|
+
return t(`bootstrap.readiness.${String(value || "").trim()}`) || value || t("locale.none");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function formatLegacyStatus(value) {
|
|
49
|
+
return t(`bootstrap.legacy.${String(value || "").trim()}`) || value || t("locale.none");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function formatMissingFields(fields) {
|
|
53
|
+
return (fields || []).map((field) => t(`bootstrap.field.${field}`) || field).join(", ");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function relativePathExists(context, relativePath) {
|
|
57
|
+
return Boolean(relativePath) && fs.existsSync(path.join(context.workspaceRoot, relativePath));
|
|
58
|
+
}
|
|
26
59
|
|
|
27
60
|
function readText(filePath) {
|
|
28
61
|
return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
|
|
@@ -88,7 +121,10 @@ function resolveOperaLocale(control, options = {}) {
|
|
|
88
121
|
|
|
89
122
|
function seedAuxiliarySkill(skillName, locale, skillsDir, options = {}) {
|
|
90
123
|
const templateDir = path.join(SKILLS_TEMPLATES_DIR, skillName);
|
|
91
|
-
if (!fs.existsSync(templateDir))
|
|
124
|
+
if (!fs.existsSync(templateDir)) {
|
|
125
|
+
process.stderr.write(`[opera] Warning: skill template not found: ${skillName} (${templateDir})\n`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
92
128
|
const targetDir = path.join(skillsDir, skillName);
|
|
93
129
|
seedDirRecursive(templateDir, targetDir, {
|
|
94
130
|
overwrite: options.rewriteLocalizedTemplates === true,
|
|
@@ -222,22 +258,36 @@ async function install(root, options = {}) {
|
|
|
222
258
|
config.saveControl(context, control);
|
|
223
259
|
env.syncEnvironment(context, control);
|
|
224
260
|
|
|
261
|
+
fmt.blank();
|
|
225
262
|
if (!alreadyInstalled) {
|
|
226
|
-
|
|
263
|
+
fmt.success(t("opera.installed", { version: OPERA_VERSION }));
|
|
227
264
|
} else {
|
|
228
|
-
|
|
265
|
+
fmt.info(t("opera.alreadyInstalled", { version: config.getOperaVersion(control) || OPERA_VERSION }));
|
|
229
266
|
}
|
|
230
267
|
|
|
231
268
|
const skills = require("./skills");
|
|
232
269
|
for (const skillName of ["commiter", "changelog-updater"]) {
|
|
233
270
|
try {
|
|
234
271
|
skills.installSkill(context, skillName, { locale });
|
|
235
|
-
} catch (
|
|
236
|
-
|
|
272
|
+
} catch (error) {
|
|
273
|
+
fmt.warn(t("skill.installError", { name: skillName, error: error.message }));
|
|
237
274
|
}
|
|
238
275
|
}
|
|
239
276
|
skills.updateRegistry(context);
|
|
240
277
|
|
|
278
|
+
if (options.bootstrap !== false && options.interactive !== false) {
|
|
279
|
+
const readline = require("readline");
|
|
280
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
281
|
+
const answer = await new Promise((resolve) => {
|
|
282
|
+
rl.question(` ${t("opera.install.bootstrapConfirm")} `, (ans) => { rl.close(); resolve(ans); });
|
|
283
|
+
});
|
|
284
|
+
const normalizedAnswer = String(answer || "").trim().toLowerCase();
|
|
285
|
+
if (!["y", "yes", "s", "si"].includes(normalizedAnswer)) {
|
|
286
|
+
fmt.info(t("opera.install.bootstrapSkipped"));
|
|
287
|
+
fmt.blank();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
241
291
|
if (options.bootstrap !== false) {
|
|
242
292
|
await runBootstrap(context, {
|
|
243
293
|
locale,
|
|
@@ -302,37 +352,85 @@ async function runBootstrap(root, options = {}) {
|
|
|
302
352
|
if ((options.resume || options.forceResume) && control.meta?.opera?.bootstrap) {
|
|
303
353
|
const resumed = bootstrap.resumeBootstrap(context, control);
|
|
304
354
|
if (resumed.resumed) {
|
|
305
|
-
const
|
|
355
|
+
const profile = resumed.profile;
|
|
356
|
+
const updatedControl = bootstrap.applyBootstrap(context, control, profile);
|
|
306
357
|
env.syncEnvironment(context, updatedControl, { requiredKeys: env.inferRequiredKeys(updatedControl, context) });
|
|
307
358
|
const ops = require("./control");
|
|
308
359
|
ops.syncDocs(context, updatedControl);
|
|
309
360
|
ops.refreshRepoRuntime(context, { quiet: true });
|
|
310
|
-
|
|
311
|
-
|
|
361
|
+
fmt.blank();
|
|
362
|
+
|
|
363
|
+
if (profile.status === "completed") {
|
|
364
|
+
fmt.success(t("bootstrap.completed"));
|
|
365
|
+
fmt.blank();
|
|
366
|
+
fmt.step(t("bootstrap.next.label"));
|
|
367
|
+
fmt.hint(t("bootstrap.next.directCompleted"));
|
|
368
|
+
} else if (profile.status === "blocked") {
|
|
369
|
+
fmt.warn(t("bootstrap.resumeBlocked"));
|
|
370
|
+
for (const field of profile.missingFields || []) {
|
|
371
|
+
fmt.bullet(t(`bootstrap.field.${field}`) || field);
|
|
372
|
+
}
|
|
373
|
+
fmt.blank();
|
|
374
|
+
fmt.hint(t("bootstrap.next.directPending"));
|
|
375
|
+
} else if (profile.status === "needs_review") {
|
|
376
|
+
fmt.info(t("bootstrap.resumeNeedsReview"));
|
|
377
|
+
for (const field of profile.missingFields || []) {
|
|
378
|
+
fmt.bullet(t(`bootstrap.field.${field}`) || field);
|
|
379
|
+
}
|
|
380
|
+
fmt.blank();
|
|
381
|
+
fmt.hint(t("bootstrap.next.directPending"));
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
fmt.blank();
|
|
385
|
+
return profile;
|
|
312
386
|
}
|
|
313
|
-
|
|
387
|
+
fmt.blank();
|
|
388
|
+
if (resumed.reason === "missing_agent_artifacts") {
|
|
389
|
+
fmt.warn(t("bootstrap.resumeAwaitingArtifacts"));
|
|
390
|
+
} else if (resumed.reason === "empty_intake_and_spec") {
|
|
391
|
+
fmt.warn(t("bootstrap.resumeEmptyArtifacts"));
|
|
392
|
+
} else {
|
|
393
|
+
fmt.info(t("bootstrap.awaitingAgent"));
|
|
394
|
+
}
|
|
395
|
+
fmt.hint(t("bootstrap.next.handoff"));
|
|
396
|
+
fmt.blank();
|
|
314
397
|
return control.meta.opera.bootstrap;
|
|
315
398
|
}
|
|
316
399
|
|
|
317
|
-
const profile = await bootstrap.collectBootstrapProfile(context, control, options);
|
|
400
|
+
const profile = await bootstrap.collectBootstrapProfile(context, control, options);
|
|
401
|
+
if (profile.mode && profile.routeReason) {
|
|
402
|
+
fmt.blank();
|
|
403
|
+
fmt.info(t("bootstrap.routingMode", {
|
|
404
|
+
mode: formatBootstrapMode(profile.mode),
|
|
405
|
+
reason: formatBootstrapReason(profile.routeReason),
|
|
406
|
+
}));
|
|
407
|
+
}
|
|
318
408
|
const updatedControl = bootstrap.applyBootstrap(context, control, profile);
|
|
319
409
|
env.syncEnvironment(context, updatedControl, { requiredKeys: env.inferRequiredKeys(updatedControl, context) });
|
|
320
410
|
const ops = require("./control");
|
|
321
411
|
ops.syncDocs(context, updatedControl);
|
|
322
412
|
ops.refreshRepoRuntime(context, { quiet: true });
|
|
323
413
|
|
|
414
|
+
fmt.header(t("bootstrap.resultHeader"));
|
|
415
|
+
|
|
324
416
|
if (profile.mode === "agent_handoff") {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
417
|
+
fmt.info(t("bootstrap.awaitingAgent"));
|
|
418
|
+
fmt.blank();
|
|
419
|
+
fmt.step(t("bootstrap.handoffFile"), profile.handoffFiles?.markdown || bootstrap.bootstrapRelativePaths(context).markdown);
|
|
420
|
+
fmt.blank();
|
|
421
|
+
fmt.step(t("bootstrap.next.label"));
|
|
422
|
+
fmt.hint(t("bootstrap.next.handoff"));
|
|
328
423
|
} else {
|
|
329
|
-
|
|
330
|
-
|
|
424
|
+
fmt.success(profile.status === "completed" ? t("bootstrap.completed") : t("bootstrap.pending"));
|
|
425
|
+
fmt.blank();
|
|
426
|
+
fmt.step(t("bootstrap.next.label"));
|
|
427
|
+
fmt.hint(
|
|
331
428
|
profile.status === "completed"
|
|
332
429
|
? t("bootstrap.next.directCompleted")
|
|
333
430
|
: t("bootstrap.next.directPending"),
|
|
334
431
|
);
|
|
335
432
|
}
|
|
433
|
+
fmt.blank();
|
|
336
434
|
return profile;
|
|
337
435
|
}
|
|
338
436
|
|
|
@@ -346,39 +444,59 @@ function status(root) {
|
|
|
346
444
|
return;
|
|
347
445
|
}
|
|
348
446
|
|
|
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
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
447
|
+
const opera = control.meta.opera;
|
|
448
|
+
const bootstrapState = opera.bootstrap || bootstrap.detectLegacyBootstrap(context, control);
|
|
449
|
+
const localeDoctor = runtimeState.doctorLocale(control.meta?.locale || null);
|
|
450
|
+
console.log(t("opera.status.version", { version: opera.version }));
|
|
451
|
+
console.log(t("opera.status.installed", { value: opera.installedAt }));
|
|
452
|
+
console.log(t("opera.status.skills", { value: (opera.skills || []).join(", ") || t("locale.none") }));
|
|
453
|
+
console.log(t("opera.status.locale", { locale: config.getLocale(control), source: formatLocaleSource(localeDoctor.source) }));
|
|
454
|
+
console.log(t("opera.status.legacy", { value: formatLegacyStatus(opera.legacyStatus || bootstrapState?.status || "supported") }));
|
|
455
|
+
console.log(t("opera.status.contractVersion", { value: opera.contractVersion || t("locale.none") }));
|
|
456
|
+
console.log(t("opera.status.contractReadiness", { value: formatContractReadiness(opera.contractReadiness || "hypothesis") }));
|
|
457
|
+
|
|
458
|
+
if (bootstrapState) {
|
|
459
|
+
console.log(t("opera.status.bootstrap", { value: formatBootstrapStatus(bootstrapState.status) }));
|
|
460
|
+
if (bootstrapState.mode) {
|
|
461
|
+
console.log(t("opera.status.mode", { value: formatBootstrapMode(bootstrapState.mode) }));
|
|
462
|
+
}
|
|
463
|
+
if (bootstrapState.routeReason) {
|
|
464
|
+
console.log(t("opera.status.route", { value: formatBootstrapReason(bootstrapState.routeReason) }));
|
|
465
|
+
}
|
|
466
|
+
if (bootstrapState.decisionOwnership) {
|
|
467
|
+
console.log(t("opera.status.ownership", { value: formatDecisionOwnership(bootstrapState.decisionOwnership) }));
|
|
468
|
+
}
|
|
469
|
+
if ((bootstrapState.missingFields || []).length) {
|
|
470
|
+
console.log(t("opera.status.missing", { value: formatMissingFields(bootstrapState.missingFields) }));
|
|
471
|
+
if (bootstrapState.mode === "agent_handoff") {
|
|
472
|
+
console.log(t("opera.status.awaitingAgentExplanation"));
|
|
473
|
+
console.log(t("opera.status.awaitingAgentAction"));
|
|
474
|
+
} else {
|
|
475
|
+
console.log(t("opera.status.directExplanation"));
|
|
476
|
+
console.log(t("opera.status.directAction", {
|
|
477
|
+
intake: bootstrapState.intakeFiles?.json || bootstrap.bootstrapRelativePaths(context).intakeJson,
|
|
478
|
+
spec: bootstrapState.intakeFiles?.specDossier || bootstrap.bootstrapRelativePaths(context).specDossier,
|
|
479
|
+
}));
|
|
480
|
+
}
|
|
481
|
+
console.log(t("opera.status.contractNotGenerated"));
|
|
482
|
+
console.log(t("opera.status.resume"));
|
|
483
|
+
}
|
|
484
|
+
if (bootstrapState.mode === "agent_handoff" && relativePathExists(context, bootstrapState.handoffFiles?.markdown)) {
|
|
485
|
+
console.log(t("opera.status.handoff", { value: bootstrapState.handoffFiles.markdown }));
|
|
486
|
+
}
|
|
487
|
+
if (relativePathExists(context, bootstrapState.intakeFiles?.json)) {
|
|
488
|
+
console.log(t("opera.status.intake", { value: bootstrapState.intakeFiles.json }));
|
|
489
|
+
}
|
|
490
|
+
if (relativePathExists(context, bootstrapState.intakeFiles?.specDossier)) {
|
|
491
|
+
console.log(t("opera.status.specDossier", { value: bootstrapState.intakeFiles.specDossier }));
|
|
492
|
+
}
|
|
493
|
+
if (relativePathExists(context, bootstrapState.reviewFiles?.openQuestions)) {
|
|
494
|
+
console.log(t("opera.status.openQuestions", { value: bootstrapState.reviewFiles.openQuestions }));
|
|
495
|
+
}
|
|
496
|
+
if (relativePathExists(context, bootstrapState.reviewFiles?.qualityReport)) {
|
|
497
|
+
console.log(t("opera.status.qualityReport", { value: bootstrapState.reviewFiles.qualityReport }));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
382
500
|
|
|
383
501
|
const checks = [
|
|
384
502
|
[context.layout === "split" ? "ops/.agent/hub/agent.md" : ".agent/hub/agent.md", fs.existsSync(path.join(context.paths.agentHubDir, "agent.md"))],
|
|
@@ -389,11 +507,11 @@ function status(root) {
|
|
|
389
507
|
[context.layout === "split" ? "ops/policy/autonomy.json" : "policy/autonomy.json", fs.existsSync(context.paths.autonomyPolicyFile)],
|
|
390
508
|
];
|
|
391
509
|
|
|
392
|
-
console.log(t("opera.status.structure"));
|
|
393
|
-
for (const [file, exists] of checks) {
|
|
394
|
-
console.log(` ${exists
|
|
395
|
-
}
|
|
396
|
-
}
|
|
510
|
+
console.log(t("opera.status.structure"));
|
|
511
|
+
for (const [file, exists] of checks) {
|
|
512
|
+
console.log(` ${fmt.boolToken(exists)} ${file}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
397
515
|
|
|
398
516
|
function configure(root, args) {
|
|
399
517
|
const context = config.ensureContext(root);
|
|
@@ -574,33 +692,77 @@ async function cmdBootstrap(root, args) {
|
|
|
574
692
|
return runBootstrap(root, options);
|
|
575
693
|
}
|
|
576
694
|
|
|
577
|
-
function cmdHandoff(root, args) {
|
|
695
|
+
function cmdHandoff(root, args) {
|
|
578
696
|
const context = config.ensureContext(root);
|
|
579
697
|
const control = config.loadControl(context);
|
|
580
698
|
const state = bootstrap.getBootstrapState(control, context) || bootstrap.detectLegacyBootstrap(context, control);
|
|
581
699
|
if (!state) {
|
|
582
700
|
throw new Error("OPERA bootstrap is not initialized.");
|
|
583
701
|
}
|
|
584
|
-
const files = bootstrap.bootstrapFilePaths(context);
|
|
585
|
-
const printMode = (args || []).includes("--print");
|
|
586
|
-
const jsonMode = (args || []).includes("--json");
|
|
587
|
-
if (
|
|
588
|
-
const payload =
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
702
|
+
const files = bootstrap.bootstrapFilePaths(context);
|
|
703
|
+
const printMode = (args || []).includes("--print");
|
|
704
|
+
const jsonMode = (args || []).includes("--json");
|
|
705
|
+
if (state.mode !== "agent_handoff") {
|
|
706
|
+
const payload = {
|
|
707
|
+
mode: state.mode,
|
|
708
|
+
status: state.status,
|
|
709
|
+
files: {
|
|
710
|
+
intakeJson: state.intakeFiles?.json || bootstrap.bootstrapRelativePaths(context).intakeJson,
|
|
711
|
+
specDossier: state.intakeFiles?.specDossier || bootstrap.bootstrapRelativePaths(context).specDossier,
|
|
712
|
+
openQuestions: state.reviewFiles?.openQuestions || bootstrap.bootstrapRelativePaths(context).openQuestions,
|
|
713
|
+
qualityReport: state.reviewFiles?.qualityReport || bootstrap.bootstrapRelativePaths(context).qualityReport,
|
|
714
|
+
},
|
|
715
|
+
nextStep: t("opera.handoff.directNext"),
|
|
716
|
+
};
|
|
717
|
+
if (jsonMode) {
|
|
718
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (printMode) {
|
|
722
|
+
process.stdout.write([
|
|
723
|
+
`# ${t("opera.handoff.directTitle")}`,
|
|
724
|
+
"",
|
|
725
|
+
`- ${t("opera.handoff.directStatus")}: ${formatBootstrapStatus(state.status)}`,
|
|
726
|
+
`- ${t("opera.handoff.directIntake")}: ${payload.files.intakeJson}`,
|
|
727
|
+
`- ${t("opera.handoff.directSpec")}: ${payload.files.specDossier}`,
|
|
728
|
+
`- ${t("opera.handoff.directQuestions")}: ${payload.files.openQuestions}`,
|
|
729
|
+
`- ${t("opera.handoff.directQuality")}: ${payload.files.qualityReport}`,
|
|
730
|
+
"",
|
|
731
|
+
t("opera.handoff.directNext"),
|
|
732
|
+
"",
|
|
733
|
+
].join("\n"));
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
console.log(t("opera.handoff.directSummary", { status: formatBootstrapStatus(state.status) }));
|
|
737
|
+
console.log(t("opera.status.intake", { value: payload.files.intakeJson }));
|
|
738
|
+
console.log(t("opera.status.specDossier", { value: payload.files.specDossier }));
|
|
739
|
+
if (relativePathExists(context, payload.files.openQuestions)) {
|
|
740
|
+
console.log(t("opera.status.openQuestions", { value: payload.files.openQuestions }));
|
|
741
|
+
}
|
|
742
|
+
if (relativePathExists(context, payload.files.qualityReport)) {
|
|
743
|
+
console.log(t("opera.status.qualityReport", { value: payload.files.qualityReport }));
|
|
744
|
+
}
|
|
745
|
+
console.log(t("opera.status.resume"));
|
|
746
|
+
console.log(t("opera.handoff.directNext"));
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (jsonMode) {
|
|
750
|
+
const payload = readText(files.json);
|
|
751
|
+
process.stdout.write(payload || "{}\n");
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
592
754
|
if (printMode) {
|
|
593
755
|
process.stdout.write(readText(files.markdown) || "");
|
|
594
756
|
return;
|
|
595
757
|
}
|
|
596
|
-
console.log(
|
|
597
|
-
console.log(
|
|
598
|
-
console.log(
|
|
599
|
-
console.log(
|
|
600
|
-
if (state.reviewFiles?.openQuestions) {
|
|
601
|
-
console.log(
|
|
602
|
-
}
|
|
603
|
-
}
|
|
758
|
+
console.log(t("opera.handoff.summary", { status: formatBootstrapStatus(state.status) }));
|
|
759
|
+
console.log(t("opera.handoff.mode", { mode: formatBootstrapMode(state.mode) }));
|
|
760
|
+
console.log(t("opera.handoff.markdown", { value: state.handoffFiles?.markdown || bootstrap.bootstrapRelativePaths(context).markdown }));
|
|
761
|
+
console.log(t("opera.handoff.json", { value: state.handoffFiles?.json || bootstrap.bootstrapRelativePaths(context).json }));
|
|
762
|
+
if (relativePathExists(context, state.reviewFiles?.openQuestions)) {
|
|
763
|
+
console.log(t("opera.status.openQuestions", { value: state.reviewFiles.openQuestions }));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
604
766
|
|
|
605
767
|
module.exports = {
|
|
606
768
|
installStructure,
|
package/lib/skills.js
CHANGED
|
@@ -6,6 +6,7 @@ const path = require("path");
|
|
|
6
6
|
const config = require("./config");
|
|
7
7
|
const { t, setLocale } = require("./i18n");
|
|
8
8
|
const { resolveSkillFile } = require("./resources");
|
|
9
|
+
const fmt = require("./cli-format");
|
|
9
10
|
|
|
10
11
|
const SKILLS_TEMPLATES_DIR = path.join(__dirname, "..", "templates", "skills");
|
|
11
12
|
const INSTALLED_SKILL_PRIORITY = [
|
|
@@ -102,11 +103,14 @@ function updateRegistry(root) {
|
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
const skills = installedSkills(root);
|
|
105
|
-
const header = locale === "en" ? "# Skills Registry" : "#
|
|
106
|
-
const empty = locale === "en"
|
|
107
|
-
? "_No skills installed yet. Use `trackops skill install <name>` to add one._"
|
|
108
|
-
: "
|
|
109
|
-
const
|
|
106
|
+
const header = locale === "en" ? "# Skills Registry" : "# Registro de skills";
|
|
107
|
+
const empty = locale === "en"
|
|
108
|
+
? "_No skills installed yet. Use `trackops skill install <name>` to add one._"
|
|
109
|
+
: "_Todavia no hay skills instaladas. Usa `trackops skill install <nombre>` para añadir una._";
|
|
110
|
+
const tableHeader = locale === "en"
|
|
111
|
+
? "| Skill | Version | Description |"
|
|
112
|
+
: "| Skill | Version | Descripcion |";
|
|
113
|
+
const lines = [header, "", tableHeader, "|-------|---------|-------------|"];
|
|
110
114
|
for (const s of skills) {
|
|
111
115
|
lines.push(`| ${s.name} | ${s.version} | ${s.description} |`);
|
|
112
116
|
}
|
|
@@ -121,7 +125,11 @@ function updateRegistry(root) {
|
|
|
121
125
|
control.meta.opera.skills = skills.map((s) => s.name);
|
|
122
126
|
config.saveControl(context, control);
|
|
123
127
|
}
|
|
124
|
-
} catch (
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (process.env.TRACKOPS_DEBUG) {
|
|
130
|
+
process.stderr.write(`[skills] Registry update error: ${error.message}\n`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
125
133
|
}
|
|
126
134
|
}
|
|
127
135
|
|
|
@@ -148,7 +156,7 @@ function installSkill(root, skillName) {
|
|
|
148
156
|
fs.copyFileSync(localizedSkill, path.join(targetDir, "SKILL.md"));
|
|
149
157
|
}
|
|
150
158
|
updateRegistry(context);
|
|
151
|
-
|
|
159
|
+
fmt.success(t("skill.installed", { name: skillName }));
|
|
152
160
|
}
|
|
153
161
|
|
|
154
162
|
function removeSkill(root, skillName) {
|
|
@@ -164,37 +172,37 @@ function removeSkill(root, skillName) {
|
|
|
164
172
|
|
|
165
173
|
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
166
174
|
updateRegistry(context);
|
|
167
|
-
|
|
175
|
+
fmt.success(t("skill.removed", { name: skillName }));
|
|
168
176
|
}
|
|
169
177
|
|
|
170
178
|
/* ── CLI commands ── */
|
|
171
179
|
|
|
172
|
-
function cmdInstall(root, skillName) {
|
|
173
|
-
if (!skillName) { console.error("
|
|
174
|
-
installSkill(root, skillName);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function cmdList(root) {
|
|
178
|
-
const skills = installedSkills(root);
|
|
179
|
-
if (!skills.length) { console.log("
|
|
180
|
-
console.log(t("skill.listTitle"));
|
|
181
|
-
for (const s of skills) {
|
|
182
|
-
console.log(` ${s.name} (v${s.version}) — ${s.description}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function cmdRemove(root, skillName) {
|
|
187
|
-
if (!skillName) { console.error("
|
|
188
|
-
removeSkill(root, skillName);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function cmdCatalog() {
|
|
192
|
-
const skills = catalogSkills();
|
|
193
|
-
if (!skills.length) { console.log("
|
|
194
|
-
console.log(t("skill.catalogTitle"));
|
|
195
|
-
for (const s of skills) {
|
|
196
|
-
console.log(` ${s.name} (v${s.version}) — ${s.description}`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
180
|
+
function cmdInstall(root, skillName) {
|
|
181
|
+
if (!skillName) { console.error(t("skill.nameRequired")); process.exit(1); }
|
|
182
|
+
installSkill(root, skillName);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function cmdList(root) {
|
|
186
|
+
const skills = installedSkills(root);
|
|
187
|
+
if (!skills.length) { console.log(t("skill.noneInstalled")); return; }
|
|
188
|
+
console.log(t("skill.listTitle"));
|
|
189
|
+
for (const s of skills) {
|
|
190
|
+
console.log(` ${s.name} (v${s.version}) — ${s.description}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function cmdRemove(root, skillName) {
|
|
195
|
+
if (!skillName) { console.error(t("skill.nameRequired")); process.exit(1); }
|
|
196
|
+
removeSkill(root, skillName);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function cmdCatalog() {
|
|
200
|
+
const skills = catalogSkills();
|
|
201
|
+
if (!skills.length) { console.log(t("skill.noneCatalog")); return; }
|
|
202
|
+
console.log(t("skill.catalogTitle"));
|
|
203
|
+
for (const s of skills) {
|
|
204
|
+
console.log(` ${s.name} (v${s.version}) — ${s.description}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
199
207
|
|
|
200
208
|
module.exports = { installSkill, removeSkill, installedSkills, catalogSkills, updateRegistry, cmdInstall, cmdList, cmdRemove, cmdCatalog };
|
package/lib/workspace.js
CHANGED
|
@@ -4,9 +4,10 @@ const fs = require("fs");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const { spawnSync } = require("child_process");
|
|
6
6
|
|
|
7
|
-
const config = require("./config");
|
|
8
|
-
const registry = require("./registry");
|
|
9
|
-
const env = require("./env");
|
|
7
|
+
const config = require("./config");
|
|
8
|
+
const registry = require("./registry");
|
|
9
|
+
const env = require("./env");
|
|
10
|
+
const { t, setLocale } = require("./i18n");
|
|
10
11
|
|
|
11
12
|
const OPS_ARTIFACTS = [
|
|
12
13
|
"project_control.json",
|
|
@@ -224,29 +225,39 @@ function migrateWorkspace(rootDir, options = {}) {
|
|
|
224
225
|
return { root, backupBranch, context };
|
|
225
226
|
}
|
|
226
227
|
|
|
227
|
-
function status(contextOrRoot) {
|
|
228
|
-
const context = config.ensureContext(contextOrRoot);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
console.log(
|
|
238
|
-
}
|
|
228
|
+
function status(contextOrRoot) {
|
|
229
|
+
const context = config.ensureContext(contextOrRoot);
|
|
230
|
+
try {
|
|
231
|
+
setLocale(config.getLocale(config.loadControl(context)));
|
|
232
|
+
} catch (_error) {
|
|
233
|
+
setLocale("es");
|
|
234
|
+
}
|
|
235
|
+
console.log(t("workspace.status.title"));
|
|
236
|
+
console.log(t("workspace.status.layout", { value: context.layout }));
|
|
237
|
+
console.log(t("workspace.status.root", { path: context.workspaceRoot }));
|
|
238
|
+
console.log(t("workspace.status.app", { path: context.appRoot }));
|
|
239
|
+
console.log(t("workspace.status.ops", { path: context.opsRoot }));
|
|
240
|
+
if (context.manifestFile) {
|
|
241
|
+
console.log(t("workspace.status.manifest", { path: context.manifestFile }));
|
|
242
|
+
}
|
|
243
|
+
console.log(t("workspace.status.control", { path: context.controlFile }));
|
|
244
|
+
}
|
|
239
245
|
|
|
240
246
|
function cmdStatus(root) {
|
|
241
247
|
status(root);
|
|
242
248
|
}
|
|
243
249
|
|
|
244
|
-
function cmdMigrate(root, args = []) {
|
|
245
|
-
const allowDirty = args.includes("--allow-dirty");
|
|
246
|
-
const result = migrateWorkspace(root, { allowDirty });
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
+
function cmdMigrate(root, args = []) {
|
|
251
|
+
const allowDirty = args.includes("--allow-dirty");
|
|
252
|
+
const result = migrateWorkspace(root, { allowDirty });
|
|
253
|
+
try {
|
|
254
|
+
setLocale(config.getLocale(config.loadControl(result.context)));
|
|
255
|
+
} catch (_error) {
|
|
256
|
+
setLocale("es");
|
|
257
|
+
}
|
|
258
|
+
console.log(t("workspace.migrate.updated", { path: result.root }));
|
|
259
|
+
console.log(t("workspace.migrate.backup", { branch: result.backupBranch }));
|
|
260
|
+
}
|
|
250
261
|
|
|
251
262
|
module.exports = {
|
|
252
263
|
OPS_ARTIFACTS,
|