trackops 1.0.1 → 2.0.0

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 (83) hide show
  1. package/README.md +292 -272
  2. package/bin/trackops.js +108 -50
  3. package/lib/config.js +267 -38
  4. package/lib/control.js +534 -480
  5. package/lib/env.js +244 -0
  6. package/lib/i18n.js +61 -53
  7. package/lib/init.js +170 -47
  8. package/lib/locale.js +63 -0
  9. package/lib/opera-bootstrap.js +1075 -0
  10. package/lib/opera.js +524 -125
  11. package/lib/preferences.js +74 -0
  12. package/lib/registry.js +27 -13
  13. package/lib/release.js +56 -0
  14. package/lib/resources.js +42 -0
  15. package/lib/runtime-state.js +144 -0
  16. package/lib/server.js +1004 -521
  17. package/lib/skills.js +148 -124
  18. package/lib/workspace.js +260 -0
  19. package/locales/en.json +418 -132
  20. package/locales/es.json +418 -132
  21. package/package.json +8 -9
  22. package/scripts/postinstall-locale.js +21 -0
  23. package/scripts/skills-marketplace-smoke.js +124 -0
  24. package/scripts/smoke-tests.js +570 -0
  25. package/scripts/sync-skill-version.js +21 -0
  26. package/scripts/validate-skill.js +89 -0
  27. package/skills/trackops/SKILL.md +89 -0
  28. package/skills/trackops/agents/openai.yaml +3 -0
  29. package/skills/trackops/references/activation.md +73 -0
  30. package/skills/trackops/references/troubleshooting.md +49 -0
  31. package/skills/trackops/references/workflow.md +26 -0
  32. package/skills/trackops/scripts/bootstrap-trackops.js +203 -0
  33. package/skills/trackops/skill.json +29 -0
  34. package/templates/opera/agent.md +10 -9
  35. package/templates/opera/architecture/dependency-graph.md +24 -0
  36. package/templates/opera/architecture/runtime-automation.md +24 -0
  37. package/templates/opera/architecture/runtime-operations.md +34 -0
  38. package/templates/opera/en/agent.md +27 -0
  39. package/templates/opera/en/architecture/dependency-graph.md +24 -0
  40. package/templates/opera/en/architecture/runtime-automation.md +24 -0
  41. package/templates/opera/en/architecture/runtime-operations.md +34 -0
  42. package/templates/opera/en/genesis.md +79 -0
  43. package/templates/opera/en/references/autonomy-and-recovery.md +23 -0
  44. package/templates/opera/en/references/opera-cycle.md +62 -0
  45. package/templates/opera/en/registry.md +28 -0
  46. package/templates/opera/en/reviews/delivery-audit.md +18 -0
  47. package/templates/opera/en/reviews/integration-audit.md +18 -0
  48. package/templates/opera/en/router.md +49 -0
  49. package/templates/opera/genesis.md +79 -94
  50. package/templates/opera/reviews/delivery-audit.md +18 -0
  51. package/templates/opera/reviews/integration-audit.md +18 -0
  52. package/templates/opera/router.md +15 -5
  53. package/templates/skills/changelog-updater/locales/en/SKILL.md +11 -0
  54. package/templates/skills/commiter/locales/en/SKILL.md +11 -0
  55. package/templates/skills/opera-contract-auditor/SKILL.md +38 -0
  56. package/templates/skills/opera-contract-auditor/locales/en/SKILL.md +38 -0
  57. package/templates/skills/opera-policy-guard/SKILL.md +26 -0
  58. package/templates/skills/opera-policy-guard/locales/en/SKILL.md +26 -0
  59. package/templates/skills/project-starter-skill/SKILL.md +89 -164
  60. package/templates/skills/project-starter-skill/locales/en/SKILL.md +104 -0
  61. package/ui/css/panels.css +956 -953
  62. package/ui/index.html +1 -1
  63. package/ui/js/api.js +211 -194
  64. package/ui/js/app.js +200 -199
  65. package/ui/js/i18n.js +14 -0
  66. package/ui/js/onboarding.js +439 -437
  67. package/ui/js/state.js +130 -129
  68. package/ui/js/utils.js +175 -172
  69. package/ui/js/views/board.js +255 -254
  70. package/ui/js/views/execution.js +256 -256
  71. package/ui/js/views/insights.js +340 -339
  72. package/ui/js/views/overview.js +366 -361
  73. package/ui/js/views/settings.js +340 -202
  74. package/ui/js/views/sidebar.js +131 -132
  75. package/ui/js/views/skills.js +163 -162
  76. package/ui/js/views/tasks.js +406 -405
  77. package/ui/js/views/topbar.js +239 -183
  78. package/templates/etapa/agent.md +0 -26
  79. package/templates/etapa/genesis.md +0 -94
  80. package/templates/etapa/references/autonomy-and-recovery.md +0 -117
  81. package/templates/etapa/references/etapa-cycle.md +0 -193
  82. package/templates/etapa/registry.md +0 -28
  83. package/templates/etapa/router.md +0 -39
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+
3
+ const config = require("./config");
4
+ const runtimeState = require("./runtime-state");
5
+ const { setLocale, t } = require("./i18n");
6
+ const { normalizeLocale } = require("./locale");
7
+
8
+ function formatLocaleSource(source) {
9
+ return t(`locale.source.${String(source || "").trim()}`) || source || t("locale.none");
10
+ }
11
+
12
+ function resolveProjectLocale(root) {
13
+ const context = config.resolveWorkspaceContext(root || process.cwd());
14
+ if (!context) return null;
15
+ try {
16
+ return config.getLocale(config.loadControl(context));
17
+ } catch (_error) {
18
+ return null;
19
+ }
20
+ }
21
+
22
+ function cmdLocale(args = [], root) {
23
+ const sub = args[0];
24
+ const projectLocale = resolveProjectLocale(root);
25
+ const doctor = runtimeState.doctorLocale(projectLocale);
26
+ setLocale(doctor.effectiveLocale);
27
+
28
+ if (sub === "get" || !sub) {
29
+ console.log(`${t("locale.effective")}: ${doctor.effectiveLocale}`);
30
+ console.log(`${t("locale.source")}: ${formatLocaleSource(doctor.source)}`);
31
+ console.log(`${t("locale.global")}: ${doctor.globalLocale || t("locale.none")}`);
32
+ if (doctor.projectLocale) {
33
+ console.log(`${t("locale.project")}: ${doctor.projectLocale}`);
34
+ }
35
+ return;
36
+ }
37
+
38
+ if (sub === "set") {
39
+ const nextLocale = normalizeLocale(args[1]);
40
+ if (!nextLocale) {
41
+ throw new Error(t("locale.invalid", { value: String(args[1] || "") }));
42
+ }
43
+ runtimeState.writeRuntimeState({ locale: nextLocale, localeSource: "manual" });
44
+ setLocale(nextLocale);
45
+ console.log(t("locale.updated", { locale: nextLocale }));
46
+ return;
47
+ }
48
+
49
+ console.log(t("cli.usage.locale"));
50
+ }
51
+
52
+ function cmdDoctor(args = [], root) {
53
+ const sub = args[0];
54
+ if (sub !== "locale") {
55
+ console.log(t("cli.usage.doctor"));
56
+ return;
57
+ }
58
+
59
+ const projectLocale = resolveProjectLocale(root);
60
+ const doctor = runtimeState.doctorLocale(projectLocale);
61
+ setLocale(doctor.effectiveLocale);
62
+ console.log(`${t("locale.effective")}: ${doctor.effectiveLocale}`);
63
+ console.log(`${t("locale.source")}: ${formatLocaleSource(doctor.source)}`);
64
+ console.log(`${t("locale.global")}: ${doctor.globalLocale || t("locale.none")}`);
65
+ console.log(`${t("locale.project")}: ${doctor.projectLocale || t("locale.none")}`);
66
+ console.log(`${t("locale.env")}: ${doctor.envLocale || t("locale.none")}`);
67
+ console.log(`${t("locale.system")}: ${doctor.systemLocale}`);
68
+ console.log(`${t("locale.runtimeFile")}: ${doctor.runtimeFile}`);
69
+ }
70
+
71
+ module.exports = {
72
+ cmdLocale,
73
+ cmdDoctor,
74
+ };
package/lib/registry.js CHANGED
@@ -4,6 +4,7 @@ const fs = require("fs");
4
4
  const os = require("os");
5
5
  const path = require("path");
6
6
 
7
+ const config = require("./config");
7
8
  const { t } = require("./i18n");
8
9
 
9
10
  const REGISTRY_DIR = path.join(os.homedir(), ".codex", "trackops");
@@ -39,9 +40,11 @@ function ensureRegistryDir() {
39
40
  function loadRegistry() {
40
41
  ensureRegistryDir();
41
42
  if (!fs.existsSync(REGISTRY_FILE)) {
42
- return { version: 1, updatedAt: nowIso(), projects: [] };
43
+ return { version: 2, updatedAt: nowIso(), projects: [] };
43
44
  }
44
- return JSON.parse(fs.readFileSync(REGISTRY_FILE, "utf8"));
45
+ const registry = JSON.parse(fs.readFileSync(REGISTRY_FILE, "utf8"));
46
+ if (!registry.version) registry.version = 1;
47
+ return registry;
45
48
  }
46
49
 
47
50
  function saveRegistry(registry) {
@@ -51,19 +54,21 @@ function saveRegistry(registry) {
51
54
  }
52
55
 
53
56
  function isProjectInstalled(rootDir) {
54
- return fs.existsSync(path.join(rootDir, "project_control.json"));
57
+ const context = config.resolveWorkspaceContext(rootDir);
58
+ return Boolean(context && fs.existsSync(context.controlFile));
55
59
  }
56
60
 
57
61
  function inspectProject(rootDir) {
58
- const root = path.resolve(rootDir);
59
- const controlStateFile = path.join(root, "project_control.json");
62
+ const context = config.resolveWorkspaceContext(rootDir) || config.createLegacyContext(rootDir);
63
+ const root = context.workspaceRoot;
64
+ const controlStateFile = context.controlFile;
60
65
 
61
66
  if (!fs.existsSync(controlStateFile)) {
62
67
  throw new Error(`Project '${root}' does not have trackops installed.`);
63
68
  }
64
69
 
65
70
  let packageJson = {};
66
- const packageFile = path.join(root, "package.json");
71
+ const packageFile = context.packageFile;
67
72
  if (fs.existsSync(packageFile)) {
68
73
  packageJson = JSON.parse(fs.readFileSync(packageFile, "utf8"));
69
74
  }
@@ -76,6 +81,10 @@ function inspectProject(rootDir) {
76
81
  id: `${slugify(projectName) || "project"}-${shortHash(root)}`,
77
82
  name: projectName,
78
83
  root,
84
+ workspaceRoot: root,
85
+ appRoot: context.appRoot,
86
+ opsRoot: context.opsRoot,
87
+ layout: context.layout,
79
88
  packageName: packageJson.name || null,
80
89
  controlStateFile,
81
90
  registeredAt: nowIso(),
@@ -86,7 +95,7 @@ function inspectProject(rootDir) {
86
95
  function registerProject(rootDir) {
87
96
  const registry = loadRegistry();
88
97
  const entry = inspectProject(rootDir);
89
- const existingIndex = registry.projects.findIndex((p) => p.root === entry.root);
98
+ const existingIndex = registry.projects.findIndex((p) => (p.workspaceRoot || p.root) === entry.workspaceRoot);
90
99
 
91
100
  if (existingIndex >= 0) {
92
101
  registry.projects[existingIndex] = {
@@ -101,13 +110,14 @@ function registerProject(rootDir) {
101
110
 
102
111
  registry.projects.sort((a, b) => a.name.localeCompare(b.name));
103
112
  saveRegistry(registry);
104
- return registry.projects.find((p) => p.root === entry.root);
113
+ return registry.projects.find((p) => (p.workspaceRoot || p.root) === entry.workspaceRoot);
105
114
  }
106
115
 
107
116
  function unregisterProject(rootDir) {
108
- const root = path.resolve(rootDir);
117
+ const context = config.resolveWorkspaceContext(rootDir) || config.createLegacyContext(rootDir);
118
+ const root = context.workspaceRoot;
109
119
  const registry = loadRegistry();
110
- registry.projects = registry.projects.filter((p) => p.root !== root);
120
+ registry.projects = registry.projects.filter((p) => (p.workspaceRoot || p.root) !== root);
111
121
  saveRegistry(registry);
112
122
  return registry;
113
123
  }
@@ -125,7 +135,9 @@ function resolveProject(projectRef, fallbackRoot) {
125
135
 
126
136
  if (!projectRef) {
127
137
  if (fallbackRoot) {
128
- return projects.find((p) => p.root === path.resolve(fallbackRoot)) || null;
138
+ const resolved = config.resolveWorkspaceContext(fallbackRoot);
139
+ const target = resolved ? resolved.workspaceRoot : path.resolve(fallbackRoot);
140
+ return projects.find((p) => (p.workspaceRoot || p.root) === target) || null;
129
141
  }
130
142
  return projects[0] || null;
131
143
  }
@@ -133,7 +145,7 @@ function resolveProject(projectRef, fallbackRoot) {
133
145
  const normalizedRef = path.resolve(projectRef);
134
146
  return (
135
147
  projects.find((p) => p.id === projectRef) ||
136
- projects.find((p) => p.root === normalizedRef) ||
148
+ projects.find((p) => (p.workspaceRoot || p.root) === normalizedRef) ||
137
149
  null
138
150
  );
139
151
  }
@@ -155,7 +167,9 @@ function cmdList() {
155
167
  projects.forEach((project, index) => {
156
168
  console.log(`${index + 1}. ${project.name}`);
157
169
  console.log(` id: ${project.id}`);
158
- console.log(` root: ${project.root}`);
170
+ console.log(` root: ${project.workspaceRoot || project.root}`);
171
+ console.log(` app: ${project.appRoot}`);
172
+ console.log(` ops: ${project.opsRoot}`);
159
173
  console.log(` available: ${project.available ? "yes" : "no"}`);
160
174
  });
161
175
  }
package/lib/release.js ADDED
@@ -0,0 +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 };
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const { normalizeLocale } = require("./locale");
7
+
8
+ function existing(filePath) {
9
+ return filePath && fs.existsSync(filePath) ? filePath : null;
10
+ }
11
+
12
+ function resolveLocalizedFile(baseDir, locale, relativePath) {
13
+ const normalized = normalizeLocale(locale) || "es";
14
+ return (
15
+ existing(path.join(baseDir, "locales", normalized, relativePath)) ||
16
+ existing(path.join(baseDir, normalized, relativePath)) ||
17
+ existing(path.join(baseDir, relativePath)) ||
18
+ existing(path.join(baseDir, "locales", "es", relativePath)) ||
19
+ existing(path.join(baseDir, "es", relativePath))
20
+ );
21
+ }
22
+
23
+ function resolveLocalizedDir(baseDir, locale, relativePath = "") {
24
+ const normalized = normalizeLocale(locale) || "es";
25
+ return (
26
+ existing(path.join(baseDir, "locales", normalized, relativePath)) ||
27
+ existing(path.join(baseDir, normalized, relativePath)) ||
28
+ existing(path.join(baseDir, relativePath)) ||
29
+ existing(path.join(baseDir, "locales", "es", relativePath)) ||
30
+ existing(path.join(baseDir, "es", relativePath))
31
+ );
32
+ }
33
+
34
+ function resolveSkillFile(templatesDir, skillName, locale) {
35
+ return resolveLocalizedFile(path.join(templatesDir, skillName), locale, "SKILL.md");
36
+ }
37
+
38
+ module.exports = {
39
+ resolveLocalizedFile,
40
+ resolveLocalizedDir,
41
+ resolveSkillFile,
42
+ };
@@ -0,0 +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
+ };