trackops 2.0.4 → 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.
Files changed (92) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +660 -575
  3. package/bin/trackops.js +127 -106
  4. package/lib/cli-format.js +118 -0
  5. package/lib/config.js +352 -326
  6. package/lib/control.js +408 -246
  7. package/lib/env.js +234 -222
  8. package/lib/i18n.js +5 -4
  9. package/lib/init.js +390 -282
  10. package/lib/locale.js +41 -41
  11. package/lib/opera-bootstrap.js +1066 -880
  12. package/lib/opera.js +615 -444
  13. package/lib/preferences.js +74 -74
  14. package/lib/registry.js +214 -214
  15. package/lib/release.js +56 -56
  16. package/lib/runtime-state.js +144 -144
  17. package/lib/skills.js +114 -89
  18. package/lib/workspace.js +259 -248
  19. package/locales/en.json +311 -167
  20. package/locales/es.json +314 -170
  21. package/package.json +61 -58
  22. package/scripts/postinstall-locale.js +21 -21
  23. package/scripts/skills-marketplace-smoke.js +124 -124
  24. package/scripts/smoke-tests.js +563 -517
  25. package/scripts/sync-skill-version.js +21 -21
  26. package/scripts/validate-skill.js +103 -103
  27. package/skills/trackops/SKILL.md +126 -122
  28. package/skills/trackops/agents/openai.yaml +7 -7
  29. package/skills/trackops/locales/en/SKILL.md +126 -122
  30. package/skills/trackops/locales/en/references/activation.md +94 -90
  31. package/skills/trackops/locales/en/references/troubleshooting.md +73 -67
  32. package/skills/trackops/locales/en/references/workflow.md +55 -32
  33. package/skills/trackops/references/activation.md +94 -90
  34. package/skills/trackops/references/troubleshooting.md +73 -67
  35. package/skills/trackops/references/workflow.md +55 -32
  36. package/skills/trackops/skill.json +29 -29
  37. package/templates/hooks/post-checkout +2 -2
  38. package/templates/hooks/post-commit +2 -2
  39. package/templates/hooks/post-merge +2 -2
  40. package/templates/opera/agent.md +28 -27
  41. package/templates/opera/architecture/dependency-graph.md +24 -24
  42. package/templates/opera/architecture/runtime-automation.md +24 -24
  43. package/templates/opera/architecture/runtime-operations.md +34 -34
  44. package/templates/opera/en/agent.md +22 -21
  45. package/templates/opera/en/architecture/dependency-graph.md +24 -24
  46. package/templates/opera/en/architecture/runtime-automation.md +24 -24
  47. package/templates/opera/en/architecture/runtime-operations.md +34 -34
  48. package/templates/opera/en/reviews/delivery-audit.md +18 -18
  49. package/templates/opera/en/reviews/integration-audit.md +18 -18
  50. package/templates/opera/en/router.md +24 -19
  51. package/templates/opera/references/autonomy-and-recovery.md +117 -117
  52. package/templates/opera/references/opera-cycle.md +193 -193
  53. package/templates/opera/registry.md +28 -28
  54. package/templates/opera/reviews/delivery-audit.md +18 -18
  55. package/templates/opera/reviews/integration-audit.md +18 -18
  56. package/templates/opera/router.md +54 -49
  57. package/templates/skills/changelog-updater/SKILL.md +69 -69
  58. package/templates/skills/commiter/SKILL.md +99 -99
  59. package/templates/skills/opera-contract-auditor/SKILL.md +38 -38
  60. package/templates/skills/opera-contract-auditor/locales/en/SKILL.md +38 -38
  61. package/templates/skills/opera-policy-guard/SKILL.md +26 -26
  62. package/templates/skills/opera-policy-guard/locales/en/SKILL.md +26 -26
  63. package/templates/skills/opera-skill/SKILL.md +279 -0
  64. package/templates/skills/opera-skill/locales/en/SKILL.md +279 -0
  65. package/templates/skills/opera-skill/locales/en/references/phase-dod.md +138 -0
  66. package/templates/skills/opera-skill/references/phase-dod.md +138 -0
  67. package/templates/skills/project-starter-skill/SKILL.md +150 -131
  68. package/templates/skills/project-starter-skill/locales/en/SKILL.md +143 -105
  69. package/templates/skills/project-starter-skill/references/opera-cycle.md +195 -193
  70. package/ui/css/base.css +284 -284
  71. package/ui/css/charts.css +425 -425
  72. package/ui/css/components.css +1107 -1107
  73. package/ui/css/onboarding.css +133 -133
  74. package/ui/css/terminal.css +125 -125
  75. package/ui/css/timeline.css +58 -58
  76. package/ui/css/tokens.css +284 -284
  77. package/ui/favicon.svg +5 -5
  78. package/ui/index.html +99 -99
  79. package/ui/js/charts.js +526 -526
  80. package/ui/js/console-logger.js +172 -172
  81. package/ui/js/filters.js +247 -247
  82. package/ui/js/icons.js +129 -129
  83. package/ui/js/keyboard.js +229 -229
  84. package/ui/js/router.js +142 -142
  85. package/ui/js/theme.js +100 -100
  86. package/ui/js/time-tracker.js +248 -248
  87. package/ui/js/views/dashboard.js +870 -870
  88. package/ui/js/views/flash.js +47 -47
  89. package/ui/js/views/projects.js +745 -745
  90. package/ui/js/views/scrum.js +476 -476
  91. package/ui/js/views/settings.js +331 -331
  92. package/ui/js/views/timeline.js +265 -265
package/lib/release.js CHANGED
@@ -1,56 +1,56 @@
1
- #!/usr/bin/env node
2
-
3
- const { spawnSync } = require("child_process");
4
- const config = require("./config");
5
-
6
- function git(args, cwd) {
7
- return spawnSync("git", args, { cwd, encoding: "utf8" });
8
- }
9
-
10
- function runGit(args, cwd, errorMessage) {
11
- const result = git(args, cwd);
12
- if (result.status !== 0) {
13
- throw new Error(result.stderr || result.stdout || errorMessage);
14
- }
15
- return result.stdout.trim();
16
- }
17
-
18
- function parseArgs(args = []) {
19
- return {
20
- push: args.includes("--push"),
21
- };
22
- }
23
-
24
- function cmdRelease(root, args = []) {
25
- const context = config.ensureContext(root);
26
- if (context.layout !== "split") {
27
- throw new Error("trackops release requires a split workspace.");
28
- }
29
-
30
- const options = parseArgs(args);
31
- const status = runGit(["status", "--porcelain"], context.workspaceRoot, "git status failed");
32
- if (context.publish.requireCleanWorktree && status.trim()) {
33
- throw new Error("trackops release requires a clean git worktree.");
34
- }
35
-
36
- const currentBranch = runGit(["branch", "--show-current"], context.workspaceRoot, "git branch failed");
37
- if (currentBranch !== context.branches.development) {
38
- throw new Error(`trackops release must run from '${context.branches.development}'.`);
39
- }
40
-
41
- const splitCommit = runGit(
42
- ["subtree", "split", "--prefix", context.publish.sourceDir, context.branches.development],
43
- context.workspaceRoot,
44
- "git subtree split failed",
45
- );
46
-
47
- runGit(["branch", "-f", context.branches.publish, splitCommit], context.workspaceRoot, "git branch update failed");
48
-
49
- if (options.push) {
50
- runGit(["push", "origin", context.branches.publish, "--force-with-lease"], context.workspaceRoot, "git push failed");
51
- }
52
-
53
- console.log(`Release branch '${context.branches.publish}' updated from '${context.branches.development}'.`);
54
- }
55
-
56
- module.exports = { cmdRelease };
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require("child_process");
4
+ const config = require("./config");
5
+
6
+ function git(args, cwd) {
7
+ return spawnSync("git", args, { cwd, encoding: "utf8" });
8
+ }
9
+
10
+ function runGit(args, cwd, errorMessage) {
11
+ const result = git(args, cwd);
12
+ if (result.status !== 0) {
13
+ throw new Error(result.stderr || result.stdout || errorMessage);
14
+ }
15
+ return result.stdout.trim();
16
+ }
17
+
18
+ function parseArgs(args = []) {
19
+ return {
20
+ push: args.includes("--push"),
21
+ };
22
+ }
23
+
24
+ function cmdRelease(root, args = []) {
25
+ const context = config.ensureContext(root);
26
+ if (context.layout !== "split") {
27
+ throw new Error("trackops release requires a split workspace.");
28
+ }
29
+
30
+ const options = parseArgs(args);
31
+ const status = runGit(["status", "--porcelain"], context.workspaceRoot, "git status failed");
32
+ if (context.publish.requireCleanWorktree && status.trim()) {
33
+ throw new Error("trackops release requires a clean git worktree.");
34
+ }
35
+
36
+ const currentBranch = runGit(["branch", "--show-current"], context.workspaceRoot, "git branch failed");
37
+ if (currentBranch !== context.branches.development) {
38
+ throw new Error(`trackops release must run from '${context.branches.development}'.`);
39
+ }
40
+
41
+ const splitCommit = runGit(
42
+ ["subtree", "split", "--prefix", context.publish.sourceDir, context.branches.development],
43
+ context.workspaceRoot,
44
+ "git subtree split failed",
45
+ );
46
+
47
+ runGit(["branch", "-f", context.branches.publish, splitCommit], context.workspaceRoot, "git branch update failed");
48
+
49
+ if (options.push) {
50
+ runGit(["push", "origin", context.branches.publish, "--force-with-lease"], context.workspaceRoot, "git push failed");
51
+ }
52
+
53
+ console.log(`Release branch '${context.branches.publish}' updated from '${context.branches.development}'.`);
54
+ }
55
+
56
+ module.exports = { cmdRelease };
@@ -1,144 +1,144 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require("fs");
4
- const os = require("os");
5
- const path = require("path");
6
-
7
- const { detectSystemLocale, isInteractive, normalizeLocale, promptForLocale } = require("./locale");
8
-
9
- function getRuntimeHome() {
10
- return process.env.TRACKOPS_BOOTSTRAP_HOME || os.homedir();
11
- }
12
-
13
- function getRuntimeDir() {
14
- return path.join(getRuntimeHome(), ".trackops");
15
- }
16
-
17
- function getRuntimeFile() {
18
- return path.join(getRuntimeDir(), "runtime.json");
19
- }
20
-
21
- function defaultRuntimeState() {
22
- return {
23
- locale: null,
24
- localeSource: null,
25
- skill: null,
26
- skillVersion: null,
27
- runtimePackage: "trackops",
28
- runtimeVersion: null,
29
- bootstrapPolicy: null,
30
- supportedAgentsV1: [],
31
- verifiedAt: null,
32
- verifiedWith: null,
33
- executable: null,
34
- };
35
- }
36
-
37
- function normalizeRuntimeState(state) {
38
- const base = defaultRuntimeState();
39
- const next = { ...base, ...(state || {}) };
40
- next.locale = normalizeLocale(next.locale);
41
- next.localeSource = next.localeSource || null;
42
- next.supportedAgentsV1 = Array.isArray(next.supportedAgentsV1) ? next.supportedAgentsV1 : [];
43
- return next;
44
- }
45
-
46
- function readRuntimeState() {
47
- const file = getRuntimeFile();
48
- if (!fs.existsSync(file)) return defaultRuntimeState();
49
- try {
50
- return normalizeRuntimeState(JSON.parse(fs.readFileSync(file, "utf8")));
51
- } catch (_error) {
52
- return defaultRuntimeState();
53
- }
54
- }
55
-
56
- function writeRuntimeState(patch) {
57
- const file = getRuntimeFile();
58
- const state = normalizeRuntimeState({ ...readRuntimeState(), ...(patch || {}) });
59
- fs.mkdirSync(path.dirname(file), { recursive: true });
60
- fs.writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`, "utf8");
61
- return state;
62
- }
63
-
64
- function getGlobalLocale() {
65
- return readRuntimeState().locale || null;
66
- }
67
-
68
- function resolveLocaleState(options = {}) {
69
- const runtime = readRuntimeState();
70
- const explicit = normalizeLocale(options.explicitLocale);
71
- if (explicit) return { locale: explicit, source: "explicit", runtime };
72
-
73
- const project = normalizeLocale(options.projectLocale);
74
- if (project) return { locale: project, source: "project", runtime };
75
-
76
- const global = normalizeLocale(runtime.locale);
77
- if (global) return { locale: global, source: "global", runtime };
78
-
79
- const envLocale = normalizeLocale(process.env.TRACKOPS_LOCALE);
80
- if (envLocale) return { locale: envLocale, source: "env", runtime };
81
-
82
- const system = detectSystemLocale();
83
- return { locale: system || "es", source: "system", runtime };
84
- }
85
-
86
- async function ensureGlobalLocale(options = {}) {
87
- const current = readRuntimeState();
88
- const explicit = normalizeLocale(options.preferredLocale);
89
- if (explicit) {
90
- return {
91
- locale: explicit,
92
- source: "explicit",
93
- runtime: writeRuntimeState({ locale: explicit, localeSource: "explicit" }),
94
- };
95
- }
96
-
97
- if (normalizeLocale(current.locale)) {
98
- return { locale: current.locale, source: "global", runtime: current };
99
- }
100
-
101
- let locale = null;
102
- let source = null;
103
- if (options.interactive !== false && isInteractive()) {
104
- locale = await promptForLocale(detectSystemLocale());
105
- source = "prompt";
106
- } else {
107
- locale = detectSystemLocale() || "es";
108
- source = "system";
109
- }
110
-
111
- return {
112
- locale,
113
- source,
114
- runtime: writeRuntimeState({ locale, localeSource: source }),
115
- };
116
- }
117
-
118
- function doctorLocale(projectLocale = null, explicitLocale = null) {
119
- const resolved = resolveLocaleState({ explicitLocale, projectLocale });
120
- const runtime = readRuntimeState();
121
- return {
122
- effectiveLocale: resolved.locale || "es",
123
- source: resolved.source,
124
- projectLocale: normalizeLocale(projectLocale),
125
- globalLocale: normalizeLocale(runtime.locale),
126
- envLocale: normalizeLocale(process.env.TRACKOPS_LOCALE),
127
- systemLocale: detectSystemLocale() || "es",
128
- runtimeFile: getRuntimeFile(),
129
- };
130
- }
131
-
132
- module.exports = {
133
- getRuntimeHome,
134
- getRuntimeDir,
135
- getRuntimeFile,
136
- defaultRuntimeState,
137
- normalizeRuntimeState,
138
- readRuntimeState,
139
- writeRuntimeState,
140
- getGlobalLocale,
141
- resolveLocaleState,
142
- ensureGlobalLocale,
143
- doctorLocale,
144
- };
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const os = require("os");
5
+ const path = require("path");
6
+
7
+ const { detectSystemLocale, isInteractive, normalizeLocale, promptForLocale } = require("./locale");
8
+
9
+ function getRuntimeHome() {
10
+ return process.env.TRACKOPS_BOOTSTRAP_HOME || os.homedir();
11
+ }
12
+
13
+ function getRuntimeDir() {
14
+ return path.join(getRuntimeHome(), ".trackops");
15
+ }
16
+
17
+ function getRuntimeFile() {
18
+ return path.join(getRuntimeDir(), "runtime.json");
19
+ }
20
+
21
+ function defaultRuntimeState() {
22
+ return {
23
+ locale: null,
24
+ localeSource: null,
25
+ skill: null,
26
+ skillVersion: null,
27
+ runtimePackage: "trackops",
28
+ runtimeVersion: null,
29
+ bootstrapPolicy: null,
30
+ supportedAgentsV1: [],
31
+ verifiedAt: null,
32
+ verifiedWith: null,
33
+ executable: null,
34
+ };
35
+ }
36
+
37
+ function normalizeRuntimeState(state) {
38
+ const base = defaultRuntimeState();
39
+ const next = { ...base, ...(state || {}) };
40
+ next.locale = normalizeLocale(next.locale);
41
+ next.localeSource = next.localeSource || null;
42
+ next.supportedAgentsV1 = Array.isArray(next.supportedAgentsV1) ? next.supportedAgentsV1 : [];
43
+ return next;
44
+ }
45
+
46
+ function readRuntimeState() {
47
+ const file = getRuntimeFile();
48
+ if (!fs.existsSync(file)) return defaultRuntimeState();
49
+ try {
50
+ return normalizeRuntimeState(JSON.parse(fs.readFileSync(file, "utf8")));
51
+ } catch (_error) {
52
+ return defaultRuntimeState();
53
+ }
54
+ }
55
+
56
+ function writeRuntimeState(patch) {
57
+ const file = getRuntimeFile();
58
+ const state = normalizeRuntimeState({ ...readRuntimeState(), ...(patch || {}) });
59
+ fs.mkdirSync(path.dirname(file), { recursive: true });
60
+ fs.writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`, "utf8");
61
+ return state;
62
+ }
63
+
64
+ function getGlobalLocale() {
65
+ return readRuntimeState().locale || null;
66
+ }
67
+
68
+ function resolveLocaleState(options = {}) {
69
+ const runtime = readRuntimeState();
70
+ const explicit = normalizeLocale(options.explicitLocale);
71
+ if (explicit) return { locale: explicit, source: "explicit", runtime };
72
+
73
+ const project = normalizeLocale(options.projectLocale);
74
+ if (project) return { locale: project, source: "project", runtime };
75
+
76
+ const global = normalizeLocale(runtime.locale);
77
+ if (global) return { locale: global, source: "global", runtime };
78
+
79
+ const envLocale = normalizeLocale(process.env.TRACKOPS_LOCALE);
80
+ if (envLocale) return { locale: envLocale, source: "env", runtime };
81
+
82
+ const system = detectSystemLocale();
83
+ return { locale: system || "es", source: "system", runtime };
84
+ }
85
+
86
+ async function ensureGlobalLocale(options = {}) {
87
+ const current = readRuntimeState();
88
+ const explicit = normalizeLocale(options.preferredLocale);
89
+ if (explicit) {
90
+ return {
91
+ locale: explicit,
92
+ source: "explicit",
93
+ runtime: writeRuntimeState({ locale: explicit, localeSource: "explicit" }),
94
+ };
95
+ }
96
+
97
+ if (normalizeLocale(current.locale)) {
98
+ return { locale: current.locale, source: "global", runtime: current };
99
+ }
100
+
101
+ let locale = null;
102
+ let source = null;
103
+ if (options.interactive !== false && isInteractive()) {
104
+ locale = await promptForLocale(detectSystemLocale());
105
+ source = "prompt";
106
+ } else {
107
+ locale = detectSystemLocale() || "es";
108
+ source = "system";
109
+ }
110
+
111
+ return {
112
+ locale,
113
+ source,
114
+ runtime: writeRuntimeState({ locale, localeSource: source }),
115
+ };
116
+ }
117
+
118
+ function doctorLocale(projectLocale = null, explicitLocale = null) {
119
+ const resolved = resolveLocaleState({ explicitLocale, projectLocale });
120
+ const runtime = readRuntimeState();
121
+ return {
122
+ effectiveLocale: resolved.locale || "es",
123
+ source: resolved.source,
124
+ projectLocale: normalizeLocale(projectLocale),
125
+ globalLocale: normalizeLocale(runtime.locale),
126
+ envLocale: normalizeLocale(process.env.TRACKOPS_LOCALE),
127
+ systemLocale: detectSystemLocale() || "es",
128
+ runtimeFile: getRuntimeFile(),
129
+ };
130
+ }
131
+
132
+ module.exports = {
133
+ getRuntimeHome,
134
+ getRuntimeDir,
135
+ getRuntimeFile,
136
+ defaultRuntimeState,
137
+ normalizeRuntimeState,
138
+ readRuntimeState,
139
+ writeRuntimeState,
140
+ getGlobalLocale,
141
+ resolveLocaleState,
142
+ ensureGlobalLocale,
143
+ doctorLocale,
144
+ };
package/lib/skills.js CHANGED
@@ -6,8 +6,17 @@ 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");
12
+ const INSTALLED_SKILL_PRIORITY = [
13
+ "opera-skill",
14
+ "project-starter-skill",
15
+ "opera-contract-auditor",
16
+ "opera-policy-guard",
17
+ "commiter",
18
+ "changelog-updater",
19
+ ];
11
20
 
12
21
  function copyDirRecursive(src, dest) {
13
22
  fs.mkdirSync(dest, { recursive: true });
@@ -22,9 +31,9 @@ function copyDirRecursive(src, dest) {
22
31
  }
23
32
  }
24
33
 
25
- function parseFrontmatter(content) {
26
- const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
27
- if (!match) return {};
34
+ function parseFrontmatter(content) {
35
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
36
+ if (!match) return {};
28
37
  const fm = {};
29
38
  for (const line of match[1].split("\n")) {
30
39
  const sep = line.indexOf(":");
@@ -34,16 +43,16 @@ function parseFrontmatter(content) {
34
43
  fm[key] = val;
35
44
  }
36
45
  }
37
- return fm;
38
- }
39
-
40
- function getSkillsDir(root) {
41
- return config.ensureContext(root).paths.skillsDir;
42
- }
43
-
44
- function getRegistryPath(root) {
45
- return config.ensureContext(root).paths.registryPath;
46
- }
46
+ return fm;
47
+ }
48
+
49
+ function getSkillsDir(root) {
50
+ return config.ensureContext(root).paths.skillsDir;
51
+ }
52
+
53
+ function getRegistryPath(root) {
54
+ return config.ensureContext(root).paths.registryPath;
55
+ }
47
56
 
48
57
  function catalogSkills() {
49
58
  if (!fs.existsSync(SKILLS_TEMPLATES_DIR)) return [];
@@ -58,38 +67,50 @@ function catalogSkills() {
58
67
  .filter(Boolean);
59
68
  }
60
69
 
61
- function installedSkills(root) {
62
- const skillsDir = getSkillsDir(root);
63
- if (!fs.existsSync(skillsDir)) return [];
64
- return fs.readdirSync(skillsDir, { withFileTypes: true })
65
- .filter((e) => e.isDirectory() && fs.existsSync(path.join(skillsDir, e.name, "SKILL.md")))
66
- .map((e) => {
67
- const fm = parseFrontmatter(fs.readFileSync(path.join(skillsDir, e.name, "SKILL.md"), "utf8"));
68
- return { name: e.name, description: fm.description || "", version: fm.version || "1.0" };
69
- })
70
- .filter(Boolean);
71
- }
70
+ function installedSkills(root) {
71
+ const skillsDir = getSkillsDir(root);
72
+ if (!fs.existsSync(skillsDir)) return [];
73
+ const skills = fs.readdirSync(skillsDir, { withFileTypes: true })
74
+ .filter((e) => e.isDirectory() && fs.existsSync(path.join(skillsDir, e.name, "SKILL.md")))
75
+ .map((e) => {
76
+ const fm = parseFrontmatter(fs.readFileSync(path.join(skillsDir, e.name, "SKILL.md"), "utf8"));
77
+ return { name: e.name, description: fm.description || "", version: fm.version || "1.0" };
78
+ })
79
+ .filter(Boolean);
80
+ skills.sort((a, b) => {
81
+ const ai = INSTALLED_SKILL_PRIORITY.indexOf(a.name);
82
+ const bi = INSTALLED_SKILL_PRIORITY.indexOf(b.name);
83
+ const ap = ai >= 0 ? ai : INSTALLED_SKILL_PRIORITY.length;
84
+ const bp = bi >= 0 ? bi : INSTALLED_SKILL_PRIORITY.length;
85
+ if (ap !== bp) return ap - bp;
86
+ return a.name.localeCompare(b.name);
87
+ });
88
+ return skills;
89
+ }
72
90
 
73
91
  function updateRegistry(root) {
74
92
  const registryPath = getRegistryPath(root);
75
93
  fs.mkdirSync(path.dirname(registryPath), { recursive: true });
76
- let locale = "es";
77
- const context = config.ensureContext(root);
78
- const controlFile = config.controlFilePath(context);
79
- if (fs.existsSync(controlFile)) {
80
- try {
81
- locale = config.getLocale(config.loadControl(context));
82
- setLocale(locale);
94
+ let locale = "es";
95
+ const context = config.ensureContext(root);
96
+ const controlFile = config.controlFilePath(context);
97
+ if (fs.existsSync(controlFile)) {
98
+ try {
99
+ locale = config.getLocale(config.loadControl(context));
100
+ setLocale(locale);
83
101
  } catch (_error) {
84
102
  setLocale("es");
85
103
  }
86
104
  }
87
105
  const skills = installedSkills(root);
88
- const header = locale === "en" ? "# Skills Registry" : "# Skills Registry";
89
- const empty = locale === "en"
90
- ? "_No skills installed yet. Use `trackops skill install <name>` to add one._"
91
- : "_Sin skills instaladas. Usa `trackops skill install <nombre>` para añadir una._";
92
- const lines = [header, "", "| Skill | Version | Description |", "|-------|---------|-------------|"];
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, "|-------|---------|-------------|"];
93
114
  for (const s of skills) {
94
115
  lines.push(`| ${s.name} | ${s.version} | ${s.description} |`);
95
116
  }
@@ -97,29 +118,33 @@ function updateRegistry(root) {
97
118
  fs.writeFileSync(registryPath, lines.join("\n") + "\n", "utf8");
98
119
 
99
120
  // Also update control meta
100
- if (fs.existsSync(controlFile)) {
101
- try {
102
- const control = config.loadControl(context);
103
- if (control.meta.opera) {
104
- control.meta.opera.skills = skills.map((s) => s.name);
105
- config.saveControl(context, control);
106
- }
107
- } catch (_e) { /* ignore */ }
121
+ if (fs.existsSync(controlFile)) {
122
+ try {
123
+ const control = config.loadControl(context);
124
+ if (control.meta.opera) {
125
+ control.meta.opera.skills = skills.map((s) => s.name);
126
+ config.saveControl(context, control);
127
+ }
128
+ } catch (error) {
129
+ if (process.env.TRACKOPS_DEBUG) {
130
+ process.stderr.write(`[skills] Registry update error: ${error.message}\n`);
131
+ }
132
+ }
108
133
  }
109
134
  }
110
135
 
111
- function installSkill(root, skillName) {
112
- const context = config.ensureContext(root);
113
- const control = config.loadControl(context);
114
- setLocale(config.getLocale(control));
115
- const locale = config.getLocale(control);
136
+ function installSkill(root, skillName) {
137
+ const context = config.ensureContext(root);
138
+ const control = config.loadControl(context);
139
+ setLocale(config.getLocale(control));
140
+ const locale = config.getLocale(control);
116
141
 
117
142
  const templateDir = path.join(SKILLS_TEMPLATES_DIR, skillName);
118
143
  if (!fs.existsSync(templateDir)) {
119
144
  throw new Error(t("skill.notFound", { name: skillName }));
120
145
  }
121
146
 
122
- const targetDir = path.join(getSkillsDir(context), skillName);
147
+ const targetDir = path.join(getSkillsDir(context), skillName);
123
148
  if (fs.existsSync(path.join(targetDir, "SKILL.md"))) {
124
149
  console.log(t("skill.alreadyInstalled", { name: skillName }));
125
150
  return;
@@ -130,54 +155,54 @@ function installSkill(root, skillName) {
130
155
  if (localizedSkill) {
131
156
  fs.copyFileSync(localizedSkill, path.join(targetDir, "SKILL.md"));
132
157
  }
133
- updateRegistry(context);
134
- console.log(t("skill.installed", { name: skillName }));
135
- }
136
-
137
- function removeSkill(root, skillName) {
138
- const context = config.ensureContext(root);
139
- const control = config.loadControl(context);
140
- setLocale(config.getLocale(control));
141
-
142
- const targetDir = path.join(getSkillsDir(context), skillName);
158
+ updateRegistry(context);
159
+ fmt.success(t("skill.installed", { name: skillName }));
160
+ }
161
+
162
+ function removeSkill(root, skillName) {
163
+ const context = config.ensureContext(root);
164
+ const control = config.loadControl(context);
165
+ setLocale(config.getLocale(control));
166
+
167
+ const targetDir = path.join(getSkillsDir(context), skillName);
143
168
  if (!fs.existsSync(targetDir)) {
144
169
  console.log(t("skill.notInstalled", { name: skillName }));
145
170
  return;
146
171
  }
147
172
 
148
173
  fs.rmSync(targetDir, { recursive: true, force: true });
149
- updateRegistry(context);
150
- console.log(t("skill.removed", { name: skillName }));
151
- }
152
-
153
- /* ── CLI commands ── */
154
-
155
- function cmdInstall(root, skillName) {
156
- if (!skillName) { console.error("Skill name required."); process.exit(1); }
157
- installSkill(root, skillName);
174
+ updateRegistry(context);
175
+ fmt.success(t("skill.removed", { name: skillName }));
158
176
  }
159
177
 
160
- function cmdList(root) {
161
- const skills = installedSkills(root);
162
- if (!skills.length) { console.log("No skills installed."); return; }
163
- console.log(t("skill.listTitle"));
164
- for (const s of skills) {
165
- console.log(` ${s.name} (v${s.version}) — ${s.description}`);
166
- }
167
- }
178
+ /* ── CLI commands ── */
168
179
 
169
- function cmdRemove(root, skillName) {
170
- if (!skillName) { console.error("Skill name required."); process.exit(1); }
171
- removeSkill(root, skillName);
172
- }
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
+ }
173
193
 
174
- function cmdCatalog() {
175
- const skills = catalogSkills();
176
- if (!skills.length) { console.log("No skills available in catalog."); return; }
177
- console.log(t("skill.catalogTitle"));
178
- for (const s of skills) {
179
- console.log(` ${s.name} (v${s.version}) — ${s.description}`);
180
- }
181
- }
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
+ }
182
207
 
183
208
  module.exports = { installSkill, removeSkill, installedSkills, catalogSkills, updateRegistry, cmdInstall, cmdList, cmdRemove, cmdCatalog };