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/control.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require("fs");
4
- const path = require("path");
5
- const { spawnSync } = require("child_process");
6
-
7
- const config = require("./config");
8
- const env = require("./env");
9
- const { t, setLocale, getLocale } = require("./i18n");
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { spawnSync } = require("child_process");
6
+
7
+ const config = require("./config");
8
+ const env = require("./env");
9
+ const { t, setLocale, getLocale } = require("./i18n");
10
10
 
11
11
  const PRIORITY_ORDER = ["P0", "P1", "P2", "P3"];
12
12
  const STATUS_ORDER = ["in_progress", "in_review", "pending", "blocked", "completed", "cancelled"];
@@ -51,14 +51,14 @@ function statusLabel(status) {
51
51
 
52
52
  /* ── repo snapshot ── */
53
53
 
54
- function getRepoSnapshot(contextOrRoot) {
55
- const context = config.ensureContext(contextOrRoot);
56
- const repoRoot = context.workspaceRoot;
57
- const branch = git(["branch", "--show-current"], repoRoot) || "detached";
58
- const status = git(["status", "--short"], repoRoot) || "";
59
- const lines = status.split(/\r?\n/).filter(Boolean);
60
- const lastCommitRaw = git(["log", "-1", "--pretty=format:%H%n%cs%n%s"], repoRoot);
61
- const divergenceRaw = git(["rev-list", "--left-right", "--count", "@{upstream}...HEAD"], repoRoot);
54
+ function getRepoSnapshot(contextOrRoot) {
55
+ const context = config.ensureContext(contextOrRoot);
56
+ const repoRoot = context.workspaceRoot;
57
+ const branch = git(["branch", "--show-current"], repoRoot) || "detached";
58
+ const status = git(["status", "--short"], repoRoot) || "";
59
+ const lines = status.split(/\r?\n/).filter(Boolean);
60
+ const lastCommitRaw = git(["log", "-1", "--pretty=format:%H%n%cs%n%s"], repoRoot);
61
+ const divergenceRaw = git(["rev-list", "--left-right", "--count", "@{upstream}...HEAD"], repoRoot);
62
62
 
63
63
  let staged = 0;
64
64
  let unstaged = 0;
@@ -87,17 +87,17 @@ function getRepoSnapshot(contextOrRoot) {
87
87
  return { generatedAt: nowIso(), branch, clean: lines.length === 0, staged, unstaged, untracked, ahead, behind, lastCommit };
88
88
  }
89
89
 
90
- function refreshRepoRuntime(root, options = {}) {
91
- const context = config.ensureContext(root);
92
- const runtimeFile = config.runtimeFilePath(context);
93
- fs.mkdirSync(path.dirname(runtimeFile), { recursive: true });
94
- const snapshot = getRepoSnapshot(context);
95
- writeJson(runtimeFile, snapshot);
96
- if (!options.quiet) {
97
- console.log(t("cli.runtimeUpdated", { path: path.relative(context.workspaceRoot, runtimeFile) }));
98
- }
99
- return snapshot;
100
- }
90
+ function refreshRepoRuntime(root, options = {}) {
91
+ const context = config.ensureContext(root);
92
+ const runtimeFile = config.runtimeFilePath(context);
93
+ fs.mkdirSync(path.dirname(runtimeFile), { recursive: true });
94
+ const snapshot = getRepoSnapshot(context);
95
+ writeJson(runtimeFile, snapshot);
96
+ if (!options.quiet) {
97
+ console.log(t("cli.runtimeUpdated", { path: path.relative(context.workspaceRoot, runtimeFile) }));
98
+ }
99
+ return snapshot;
100
+ }
101
101
 
102
102
  /* ── derive ── */
103
103
 
@@ -115,11 +115,11 @@ function compareTasks(a, b, phases) {
115
115
  return a.title.localeCompare(b.title, getLocale());
116
116
  }
117
117
 
118
- function derive(control) {
119
- const phases = config.getPhases(control);
120
- const tasks = [...control.tasks].sort((a, b) => compareTasks(a, b, phases));
121
- const completedIds = new Set(tasks.filter((t) => t.status === "completed").map((t) => t.id));
122
- const closedStatuses = new Set(["completed", "cancelled"]);
118
+ function derive(control) {
119
+ const phases = config.getPhases(control);
120
+ const tasks = [...control.tasks].sort((a, b) => compareTasks(a, b, phases));
121
+ const completedIds = new Set(tasks.filter((t) => t.status === "completed").map((t) => t.id));
122
+ const closedStatuses = new Set(["completed", "cancelled"]);
123
123
 
124
124
  const readyTasks = tasks
125
125
  .filter((task) => {
@@ -144,12 +144,12 @@ function derive(control) {
144
144
  phases.find((p) => requiredOpenTasks.some((t) => t.phase === p.id)) ||
145
145
  phases[phases.length - 1];
146
146
 
147
- const phaseStats = phases.map((phase) => {
148
- const phaseTasks = tasks.filter((t) => t.phase === phase.id && t.required !== false);
149
- const completed = phaseTasks.filter((t) => t.status === "completed").length;
150
- const closed = phaseTasks.filter((t) => closedStatuses.has(t.status)).length;
151
- return { ...phase, total: phaseTasks.length, completed, closed, remaining: phaseTasks.length - closed };
152
- });
147
+ const phaseStats = phases.map((phase) => {
148
+ const phaseTasks = tasks.filter((t) => t.phase === phase.id && t.required !== false);
149
+ const completed = phaseTasks.filter((t) => t.status === "completed").length;
150
+ const closed = phaseTasks.filter((t) => closedStatuses.has(t.status)).length;
151
+ return { ...phase, total: phaseTasks.length, completed, closed, remaining: phaseTasks.length - closed };
152
+ });
153
153
 
154
154
  const nextTask = activeTasks[0] || readyTasks[0] || blockers[0] || openTasks[0] || null;
155
155
 
@@ -359,11 +359,11 @@ function buildDocMap(control) {
359
359
  return { taskPlan: renderTaskPlan(control), progress: renderProgress(control), findings: renderFindings(control) };
360
360
  }
361
361
 
362
- function getDocDrift(root, control) {
363
- const context = config.ensureContext(root);
364
- const docs = buildDocMap(control);
365
- const docFiles = config.docFilePaths(context);
366
- return Object.entries({ task_plan: [docFiles.taskPlan, docs.taskPlan], progress: [docFiles.progress, docs.progress], findings: [docFiles.findings, docs.findings] })
362
+ function getDocDrift(root, control) {
363
+ const context = config.ensureContext(root);
364
+ const docs = buildDocMap(control);
365
+ const docFiles = config.docFilePaths(context);
366
+ return Object.entries({ task_plan: [docFiles.taskPlan, docs.taskPlan], progress: [docFiles.progress, docs.progress], findings: [docFiles.findings, docs.findings] })
367
367
  .filter(([, [filePath, expected]]) => {
368
368
  if (!fs.existsSync(filePath)) return true;
369
369
  return fs.readFileSync(filePath, "utf8").replace(/\r\n/g, "\n") !== `${expected}\n`;
@@ -371,21 +371,21 @@ function getDocDrift(root, control) {
371
371
  .map(([name]) => name);
372
372
  }
373
373
 
374
- function syncDocs(root, control) {
375
- const context = config.ensureContext(root);
376
- const docs = buildDocMap(control);
377
- const docFiles = config.docFilePaths(context);
378
- writeText(docFiles.taskPlan, `${docs.taskPlan}\n`);
379
- writeText(docFiles.progress, `${docs.progress}\n`);
380
- writeText(docFiles.findings, `${docs.findings}\n`);
381
- }
374
+ function syncDocs(root, control) {
375
+ const context = config.ensureContext(root);
376
+ const docs = buildDocMap(control);
377
+ const docFiles = config.docFilePaths(context);
378
+ writeText(docFiles.taskPlan, `${docs.taskPlan}\n`);
379
+ writeText(docFiles.progress, `${docs.progress}\n`);
380
+ writeText(docFiles.findings, `${docs.findings}\n`);
381
+ }
382
382
 
383
383
  /* ── task management ── */
384
384
 
385
- function updateTask(root, control, action, taskId, note) {
386
- const context = config.ensureContext(root);
387
- const task = control.tasks.find((item) => item.id === taskId);
388
- if (!task) throw new Error(t("cli.taskNotFound", { taskId }));
385
+ function updateTask(root, control, action, taskId, note) {
386
+ const context = config.ensureContext(root);
387
+ const task = control.tasks.find((item) => item.id === taskId);
388
+ if (!task) throw new Error(t("cli.taskNotFound", { taskId }));
389
389
 
390
390
  const actionMap = { start: "in_progress", review: "in_review", complete: "completed", done: "completed", block: "blocked", pending: "pending", cancel: "cancelled" };
391
391
 
@@ -406,49 +406,49 @@ function updateTask(root, control, action, taskId, note) {
406
406
  task.history.push({ at: nowIso(), action, note: note || "" });
407
407
  }
408
408
 
409
- config.saveControl(context, control);
410
- syncDocs(context, control);
411
- refreshRepoRuntime(context, { quiet: true });
412
- }
409
+ config.saveControl(context, control);
410
+ syncDocs(context, control);
411
+ refreshRepoRuntime(context, { quiet: true });
412
+ }
413
413
 
414
414
  /* ── CLI commands ── */
415
415
 
416
- function initLocale(root) {
417
- try {
418
- const control = config.loadControl(config.ensureContext(root));
419
- setLocale(config.getLocale(control));
420
- } catch (_err) {
421
- setLocale("es");
416
+ function initLocale(root) {
417
+ try {
418
+ const control = config.loadControl(config.ensureContext(root));
419
+ setLocale(config.getLocale(control));
420
+ } catch (_err) {
421
+ setLocale("es");
422
422
  }
423
423
  }
424
424
 
425
- function cmdStatus(root) {
426
- const context = config.ensureContext(root);
427
- initLocale(context);
428
- const control = config.loadControl(context);
429
- const state = derive(control);
430
- const phases = config.getPhases(control);
431
- const repo = refreshRepoRuntime(context, { quiet: true });
432
- const drift = getDocDrift(context, control);
433
- const envAudit = env.auditEnvironment(context, control);
434
-
435
- console.log(t("cli.status.title", { projectName: control.meta.projectName }));
436
- console.log(t("cli.status.focus", { focus: control.meta.currentFocus }));
437
- console.log(t("cli.status.activePhase", { phaseId: state.activePhase.id, phaseLabel: state.activePhase.label }));
438
- console.log(`Layout: ${context.layout} | Workspace: ${context.workspaceRoot}`);
439
- if (context.layout === "split") {
440
- console.log(`App: ${context.appRoot}`);
441
- console.log(`Ops: ${context.opsRoot}`);
442
- }
443
- if (control.meta?.opera?.bootstrap?.status) {
444
- console.log(t("cli.status.bootstrap", { status: control.meta.opera.bootstrap.status, locale: config.getLocale(control) }));
445
- if (control.meta.opera.bootstrap.mode) {
446
- console.log(`Bootstrap mode: ${control.meta.opera.bootstrap.mode}`);
447
- }
448
- if (control.meta.opera.bootstrap.routeReason) {
449
- console.log(`Bootstrap route: ${control.meta.opera.bootstrap.routeReason}`);
450
- }
451
- }
425
+ function cmdStatus(root) {
426
+ const context = config.ensureContext(root);
427
+ initLocale(context);
428
+ const control = config.loadControl(context);
429
+ const state = derive(control);
430
+ const phases = config.getPhases(control);
431
+ const repo = refreshRepoRuntime(context, { quiet: true });
432
+ const drift = getDocDrift(context, control);
433
+ const envAudit = env.auditEnvironment(context, control);
434
+
435
+ console.log(t("cli.status.title", { projectName: control.meta.projectName }));
436
+ console.log(t("cli.status.focus", { focus: control.meta.currentFocus }));
437
+ console.log(t("cli.status.activePhase", { phaseId: state.activePhase.id, phaseLabel: state.activePhase.label }));
438
+ console.log(`Layout: ${context.layout} | Workspace: ${context.workspaceRoot}`);
439
+ if (context.layout === "split") {
440
+ console.log(`App: ${context.appRoot}`);
441
+ console.log(`Ops: ${context.opsRoot}`);
442
+ }
443
+ if (control.meta?.opera?.bootstrap?.status) {
444
+ console.log(t("cli.status.bootstrap", { status: control.meta.opera.bootstrap.status, locale: config.getLocale(control) }));
445
+ if (control.meta.opera.bootstrap.mode) {
446
+ console.log(`Bootstrap mode: ${control.meta.opera.bootstrap.mode}`);
447
+ }
448
+ if (control.meta.opera.bootstrap.routeReason) {
449
+ console.log(`Bootstrap route: ${control.meta.opera.bootstrap.routeReason}`);
450
+ }
451
+ }
452
452
  console.log(t("cli.status.tasks", {
453
453
  completed: state.totals.completed, inProgress: state.totals.inProgress,
454
454
  inReview: state.totals.inReview, pending: state.totals.pending, blocked: state.totals.blocked,
@@ -485,22 +485,22 @@ function cmdStatus(root) {
485
485
  }
486
486
  if (repo.ahead || repo.behind) {
487
487
  console.log(`- ${t("cli.status.divergence", { ahead: repo.ahead, behind: repo.behind })}`);
488
- }
489
- console.log(`- ${t("cli.status.runtime", { path: path.relative(context.workspaceRoot, config.runtimeFilePath(context)) })}`);
490
- console.log(`- Env present: ${envAudit.presentKeys.length ? envAudit.presentKeys.join(", ") : "none"}`);
491
- console.log(`- Env missing: ${envAudit.missingKeys.length ? envAudit.missingKeys.join(", ") : "none"}`);
492
- console.log("");
493
- const syncStatus = drift.length
494
- ? t("cli.status.docsSyncedNo", { files: drift.join(", ") })
488
+ }
489
+ console.log(`- ${t("cli.status.runtime", { path: path.relative(context.workspaceRoot, config.runtimeFilePath(context)) })}`);
490
+ console.log(`- Env present: ${envAudit.presentKeys.length ? envAudit.presentKeys.join(", ") : "none"}`);
491
+ console.log(`- Env missing: ${envAudit.missingKeys.length ? envAudit.missingKeys.join(", ") : "none"}`);
492
+ console.log("");
493
+ const syncStatus = drift.length
494
+ ? t("cli.status.docsSyncedNo", { files: drift.join(", ") })
495
495
  : t("cli.status.docsSyncedYes");
496
496
  console.log(t("cli.status.docsSynced", { status: syncStatus }));
497
497
  }
498
498
 
499
- function cmdNext(root) {
500
- const context = config.ensureContext(root);
501
- initLocale(context);
502
- const control = config.loadControl(context);
503
- const ready = derive(control).readyTasks.slice(0, 10);
499
+ function cmdNext(root) {
500
+ const context = config.ensureContext(root);
501
+ initLocale(context);
502
+ const control = config.loadControl(context);
503
+ const ready = derive(control).readyTasks.slice(0, 10);
504
504
  if (!ready.length) {
505
505
  console.log(t("cli.noReadyTasks"));
506
506
  return;
@@ -513,114 +513,114 @@ function cmdNext(root) {
513
513
  });
514
514
  }
515
515
 
516
- function cmdSync(root) {
517
- const context = config.ensureContext(root);
518
- initLocale(context);
519
- const control = config.loadControl(context);
520
- env.syncEnvironment(context, control);
521
- syncDocs(context, control);
522
- refreshRepoRuntime(context, { quiet: true });
523
- console.log(t("cli.docsSynced"));
524
- }
525
-
526
- function cmdRefreshRepo(root, args) {
527
- refreshRepoRuntime(config.ensureContext(root), { quiet: (args || []).includes("--quiet") });
528
- }
529
-
530
- function cmdTask(root, args) {
531
- const context = config.ensureContext(root);
532
- initLocale(context);
533
- const [action, taskId, ...noteParts] = args || [];
534
- if (!action || !taskId) throw new Error(t("cli.mustProvideActionAndId"));
535
- const control = config.loadControl(context);
536
- updateTask(context, control, action, taskId, noteParts.join(" ").trim());
537
- console.log(t("cli.taskUpdated", { taskId, action }));
538
- }
539
-
540
- function cmdInstallHooks(root) {
541
- const context = config.ensureContext(root);
542
- initLocale(context);
543
- const hooksPath = context.layout === "split" ? "ops/.githooks" : ".githooks";
544
- const result = spawnSync("git", ["config", "core.hooksPath", hooksPath], { cwd: context.workspaceRoot, encoding: "utf8" });
545
- if (result.error || result.status !== 0) throw new Error(t("cli.hooksError"));
546
- console.log(t("cli.hooksInstalled"));
547
- }
548
-
549
- function cmdHelp() {
550
- console.log(`trackops — ${t("cli.help.title")}`);
551
- console.log("");
552
- console.log(`${t("cli.help.usage")} trackops <command> [args]`);
553
- console.log("");
554
- console.log(t("cli.help.commands"));
555
- console.log(" init [--with-opera] [--legacy-layout] [--locale es|en] [--name \"...\"] [--no-bootstrap]");
556
- console.log(" [--bootstrap-mode auto|direct|handoff] [--technical-level low|medium|high|senior]");
557
- console.log(" [--project-state idea|draft|existing_repo|advanced] [--docs-state none|notes|sos|spec_dossier|repo_docs]");
558
- console.log(" [--decision-ownership user|shared|agent]");
559
- console.log(` ${t("cli.help.init.desc")}`);
560
- console.log(" workspace status|migrate");
561
- console.log(` ${t("cli.help.workspace.desc")}`);
562
- console.log(" env status|sync");
563
- console.log(` ${t("cli.help.env.desc")}`);
564
- console.log(" release [--push]");
565
- console.log(` ${t("cli.help.release.desc")}`);
566
- console.log(" version");
567
- console.log(` ${t("cli.help.version.desc")}`);
568
- console.log(" status");
569
- console.log(` ${t("cli.help.status.desc")}`);
570
- console.log(" next");
571
- console.log(` ${t("cli.help.next.desc")}`);
572
- console.log(" sync");
573
- console.log(` ${t("cli.help.sync.desc")}`);
574
- console.log(" dashboard [--port N] [--host HOST] [--public] [--strict-port]");
575
- console.log(` ${t("cli.help.dashboard.desc")}`);
576
- console.log(" refresh-repo [--quiet]");
577
- console.log(` ${t("cli.help.refreshRepo.desc")}`);
578
- console.log(" install-hooks");
579
- console.log(` ${t("cli.help.installHooks.desc")}`);
580
- console.log(" register");
581
- console.log(` ${t("cli.help.register.desc")}`);
582
- console.log(" projects");
583
- console.log(` ${t("cli.help.projects.desc")}`);
584
- console.log(" task <action> <id> [note]");
585
- console.log(` ${t("cli.help.task.desc")}`);
586
- console.log(" opera install|bootstrap|handoff|status|configure|upgrade");
587
- console.log(` ${t("cli.help.opera.desc")}`);
588
- console.log(` ${t("cli.help.opera.upgradeHint")}`);
589
- console.log(" locale get|set [es|en]");
590
- console.log(` ${t("cli.help.locale.desc")}`);
591
- console.log(" doctor locale");
592
- console.log(` ${t("cli.help.doctor.desc")}`);
593
- console.log(" skill install|list|remove|catalog <name>");
594
- console.log(` ${t("cli.help.skill.desc")}`);
595
- console.log(" help");
596
- console.log(` ${t("cli.help.help.desc")}`);
597
- console.log("");
598
- console.log(t("cli.help.globalWorkflow"));
599
- console.log(` ${t("cli.help.globalWorkflow.line1")}`);
600
- console.log(` ${t("cli.help.globalWorkflow.line2")}`);
601
- }
516
+ function cmdSync(root) {
517
+ const context = config.ensureContext(root);
518
+ initLocale(context);
519
+ const control = config.loadControl(context);
520
+ env.syncEnvironment(context, control);
521
+ syncDocs(context, control);
522
+ refreshRepoRuntime(context, { quiet: true });
523
+ console.log(t("cli.docsSynced"));
524
+ }
525
+
526
+ function cmdRefreshRepo(root, args) {
527
+ refreshRepoRuntime(config.ensureContext(root), { quiet: (args || []).includes("--quiet") });
528
+ }
529
+
530
+ function cmdTask(root, args) {
531
+ const context = config.ensureContext(root);
532
+ initLocale(context);
533
+ const [action, taskId, ...noteParts] = args || [];
534
+ if (!action || !taskId) throw new Error(t("cli.mustProvideActionAndId"));
535
+ const control = config.loadControl(context);
536
+ updateTask(context, control, action, taskId, noteParts.join(" ").trim());
537
+ console.log(t("cli.taskUpdated", { taskId, action }));
538
+ }
539
+
540
+ function cmdInstallHooks(root) {
541
+ const context = config.ensureContext(root);
542
+ initLocale(context);
543
+ const hooksPath = context.layout === "split" ? "ops/.githooks" : ".githooks";
544
+ const result = spawnSync("git", ["config", "core.hooksPath", hooksPath], { cwd: context.workspaceRoot, encoding: "utf8" });
545
+ if (result.error || result.status !== 0) throw new Error(t("cli.hooksError"));
546
+ console.log(t("cli.hooksInstalled"));
547
+ }
548
+
549
+ function cmdHelp() {
550
+ console.log(`trackops — ${t("cli.help.title")}`);
551
+ console.log("");
552
+ console.log(`${t("cli.help.usage")} trackops <command> [args]`);
553
+ console.log("");
554
+ console.log(t("cli.help.commands"));
555
+ console.log(" init [--with-opera] [--legacy-layout] [--locale es|en] [--name \"...\"] [--no-bootstrap]");
556
+ console.log(" [--bootstrap-mode auto|direct|handoff] [--technical-level low|medium|high|senior]");
557
+ console.log(" [--project-state idea|draft|existing_repo|advanced] [--docs-state none|notes|sos|spec_dossier|repo_docs]");
558
+ console.log(" [--decision-ownership user|shared|agent]");
559
+ console.log(` ${t("cli.help.init.desc")}`);
560
+ console.log(" workspace status|migrate");
561
+ console.log(` ${t("cli.help.workspace.desc")}`);
562
+ console.log(" env status|sync");
563
+ console.log(` ${t("cli.help.env.desc")}`);
564
+ console.log(" release [--push]");
565
+ console.log(` ${t("cli.help.release.desc")}`);
566
+ console.log(" version");
567
+ console.log(` ${t("cli.help.version.desc")}`);
568
+ console.log(" status");
569
+ console.log(` ${t("cli.help.status.desc")}`);
570
+ console.log(" next");
571
+ console.log(` ${t("cli.help.next.desc")}`);
572
+ console.log(" sync");
573
+ console.log(` ${t("cli.help.sync.desc")}`);
574
+ console.log(" dashboard [--port N] [--host HOST] [--public] [--strict-port]");
575
+ console.log(` ${t("cli.help.dashboard.desc")}`);
576
+ console.log(" refresh-repo [--quiet]");
577
+ console.log(` ${t("cli.help.refreshRepo.desc")}`);
578
+ console.log(" install-hooks");
579
+ console.log(` ${t("cli.help.installHooks.desc")}`);
580
+ console.log(" register");
581
+ console.log(` ${t("cli.help.register.desc")}`);
582
+ console.log(" projects");
583
+ console.log(` ${t("cli.help.projects.desc")}`);
584
+ console.log(" task <action> <id> [note]");
585
+ console.log(` ${t("cli.help.task.desc")}`);
586
+ console.log(" opera install|bootstrap|handoff|status|configure|upgrade");
587
+ console.log(` ${t("cli.help.opera.desc")}`);
588
+ console.log(` ${t("cli.help.opera.upgradeHint")}`);
589
+ console.log(" locale get|set [es|en]");
590
+ console.log(` ${t("cli.help.locale.desc")}`);
591
+ console.log(" doctor locale");
592
+ console.log(` ${t("cli.help.doctor.desc")}`);
593
+ console.log(" skill install|list|remove|catalog <name>");
594
+ console.log(` ${t("cli.help.skill.desc")}`);
595
+ console.log(" help");
596
+ console.log(` ${t("cli.help.help.desc")}`);
597
+ console.log("");
598
+ console.log(t("cli.help.globalWorkflow"));
599
+ console.log(` ${t("cli.help.globalWorkflow.line1")}`);
600
+ console.log(` ${t("cli.help.globalWorkflow.line2")}`);
601
+ }
602
602
 
603
603
  /* ── project-scoped API (used by server) ── */
604
604
 
605
- function forProject(root) {
606
- const context = config.ensureContext(root);
607
- initLocale(context);
608
- return {
609
- loadControl: () => config.loadControl(context),
610
- saveControl: (ctrl) => config.saveControl(context, ctrl),
611
- derive,
612
- buildDocMap,
613
- getDocDrift: (ctrl) => getDocDrift(context, ctrl),
614
- syncDocs: (ctrl) => syncDocs(context, ctrl),
615
- updateTask: (ctrl, action, id, note) => updateTask(context, ctrl, action, id, note),
616
- getRepoSnapshot: () => getRepoSnapshot(context),
617
- refreshRepoRuntime: (opts) => refreshRepoRuntime(context, opts),
618
- getPhases: (ctrl) => config.getPhases(ctrl),
619
- getLocale: (ctrl) => config.getLocale(ctrl),
620
- statusLabel,
621
- context,
622
- };
623
- }
605
+ function forProject(root) {
606
+ const context = config.ensureContext(root);
607
+ initLocale(context);
608
+ return {
609
+ loadControl: () => config.loadControl(context),
610
+ saveControl: (ctrl) => config.saveControl(context, ctrl),
611
+ derive,
612
+ buildDocMap,
613
+ getDocDrift: (ctrl) => getDocDrift(context, ctrl),
614
+ syncDocs: (ctrl) => syncDocs(context, ctrl),
615
+ updateTask: (ctrl, action, id, note) => updateTask(context, ctrl, action, id, note),
616
+ getRepoSnapshot: () => getRepoSnapshot(context),
617
+ refreshRepoRuntime: (opts) => refreshRepoRuntime(context, opts),
618
+ getPhases: (ctrl) => config.getPhases(ctrl),
619
+ getLocale: (ctrl) => config.getLocale(ctrl),
620
+ statusLabel,
621
+ context,
622
+ };
623
+ }
624
624
 
625
625
  module.exports = {
626
626
  buildDocMap, derive, getDocDrift, getRepoSnapshot, refreshRepoRuntime, syncDocs, updateTask,