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/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
@@ -8,6 +8,14 @@ const { t, setLocale } = require("./i18n");
8
8
  const { resolveSkillFile } = require("./resources");
9
9
 
10
10
  const SKILLS_TEMPLATES_DIR = path.join(__dirname, "..", "templates", "skills");
11
+ const INSTALLED_SKILL_PRIORITY = [
12
+ "opera-skill",
13
+ "project-starter-skill",
14
+ "opera-contract-auditor",
15
+ "opera-policy-guard",
16
+ "commiter",
17
+ "changelog-updater",
18
+ ];
11
19
 
12
20
  function copyDirRecursive(src, dest) {
13
21
  fs.mkdirSync(dest, { recursive: true });
@@ -22,9 +30,9 @@ function copyDirRecursive(src, dest) {
22
30
  }
23
31
  }
24
32
 
25
- function parseFrontmatter(content) {
26
- const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
27
- if (!match) return {};
33
+ function parseFrontmatter(content) {
34
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
35
+ if (!match) return {};
28
36
  const fm = {};
29
37
  for (const line of match[1].split("\n")) {
30
38
  const sep = line.indexOf(":");
@@ -34,16 +42,16 @@ function parseFrontmatter(content) {
34
42
  fm[key] = val;
35
43
  }
36
44
  }
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
- }
45
+ return fm;
46
+ }
47
+
48
+ function getSkillsDir(root) {
49
+ return config.ensureContext(root).paths.skillsDir;
50
+ }
51
+
52
+ function getRegistryPath(root) {
53
+ return config.ensureContext(root).paths.registryPath;
54
+ }
47
55
 
48
56
  function catalogSkills() {
49
57
  if (!fs.existsSync(SKILLS_TEMPLATES_DIR)) return [];
@@ -58,28 +66,37 @@ function catalogSkills() {
58
66
  .filter(Boolean);
59
67
  }
60
68
 
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
- }
69
+ function installedSkills(root) {
70
+ const skillsDir = getSkillsDir(root);
71
+ if (!fs.existsSync(skillsDir)) return [];
72
+ const skills = fs.readdirSync(skillsDir, { withFileTypes: true })
73
+ .filter((e) => e.isDirectory() && fs.existsSync(path.join(skillsDir, e.name, "SKILL.md")))
74
+ .map((e) => {
75
+ const fm = parseFrontmatter(fs.readFileSync(path.join(skillsDir, e.name, "SKILL.md"), "utf8"));
76
+ return { name: e.name, description: fm.description || "", version: fm.version || "1.0" };
77
+ })
78
+ .filter(Boolean);
79
+ skills.sort((a, b) => {
80
+ const ai = INSTALLED_SKILL_PRIORITY.indexOf(a.name);
81
+ const bi = INSTALLED_SKILL_PRIORITY.indexOf(b.name);
82
+ const ap = ai >= 0 ? ai : INSTALLED_SKILL_PRIORITY.length;
83
+ const bp = bi >= 0 ? bi : INSTALLED_SKILL_PRIORITY.length;
84
+ if (ap !== bp) return ap - bp;
85
+ return a.name.localeCompare(b.name);
86
+ });
87
+ return skills;
88
+ }
72
89
 
73
90
  function updateRegistry(root) {
74
91
  const registryPath = getRegistryPath(root);
75
92
  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);
93
+ let locale = "es";
94
+ const context = config.ensureContext(root);
95
+ const controlFile = config.controlFilePath(context);
96
+ if (fs.existsSync(controlFile)) {
97
+ try {
98
+ locale = config.getLocale(config.loadControl(context));
99
+ setLocale(locale);
83
100
  } catch (_error) {
84
101
  setLocale("es");
85
102
  }
@@ -97,29 +114,29 @@ function updateRegistry(root) {
97
114
  fs.writeFileSync(registryPath, lines.join("\n") + "\n", "utf8");
98
115
 
99
116
  // 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
- }
117
+ if (fs.existsSync(controlFile)) {
118
+ try {
119
+ const control = config.loadControl(context);
120
+ if (control.meta.opera) {
121
+ control.meta.opera.skills = skills.map((s) => s.name);
122
+ config.saveControl(context, control);
123
+ }
107
124
  } catch (_e) { /* ignore */ }
108
125
  }
109
126
  }
110
127
 
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);
128
+ function installSkill(root, skillName) {
129
+ const context = config.ensureContext(root);
130
+ const control = config.loadControl(context);
131
+ setLocale(config.getLocale(control));
132
+ const locale = config.getLocale(control);
116
133
 
117
134
  const templateDir = path.join(SKILLS_TEMPLATES_DIR, skillName);
118
135
  if (!fs.existsSync(templateDir)) {
119
136
  throw new Error(t("skill.notFound", { name: skillName }));
120
137
  }
121
138
 
122
- const targetDir = path.join(getSkillsDir(context), skillName);
139
+ const targetDir = path.join(getSkillsDir(context), skillName);
123
140
  if (fs.existsSync(path.join(targetDir, "SKILL.md"))) {
124
141
  console.log(t("skill.alreadyInstalled", { name: skillName }));
125
142
  return;
@@ -130,25 +147,25 @@ function installSkill(root, skillName) {
130
147
  if (localizedSkill) {
131
148
  fs.copyFileSync(localizedSkill, path.join(targetDir, "SKILL.md"));
132
149
  }
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);
150
+ updateRegistry(context);
151
+ console.log(t("skill.installed", { name: skillName }));
152
+ }
153
+
154
+ function removeSkill(root, skillName) {
155
+ const context = config.ensureContext(root);
156
+ const control = config.loadControl(context);
157
+ setLocale(config.getLocale(control));
158
+
159
+ const targetDir = path.join(getSkillsDir(context), skillName);
143
160
  if (!fs.existsSync(targetDir)) {
144
161
  console.log(t("skill.notInstalled", { name: skillName }));
145
162
  return;
146
163
  }
147
164
 
148
165
  fs.rmSync(targetDir, { recursive: true, force: true });
149
- updateRegistry(context);
150
- console.log(t("skill.removed", { name: skillName }));
151
- }
166
+ updateRegistry(context);
167
+ console.log(t("skill.removed", { name: skillName }));
168
+ }
152
169
 
153
170
  /* ── CLI commands ── */
154
171