trackops 2.0.3 → 2.0.5

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