trackops 2.1.0 → 2.2.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.
- package/README.md +14 -2
- package/lib/opera.js +1 -1
- package/lib/skills.js +94 -41
- package/package.json +6 -6
- package/scripts/skills-marketplace-smoke.js +156 -124
- package/scripts/smoke-tests.js +21 -14
- package/scripts/sync-skill-version.js +29 -19
- package/scripts/validate-skill.js +188 -103
- package/skills/trackops/SKILL.md +25 -7
- package/skills/trackops/locales/en/SKILL.md +25 -7
- package/skills/trackops/locales/en/references/activation.md +3 -3
- package/skills/trackops/locales/en/references/workflow.md +5 -4
- package/skills/trackops/references/activation.md +3 -3
- package/skills/trackops/references/workflow.md +5 -4
- package/skills/trackops/skill.json +2 -2
- package/skills/trackops-quality-guard/SKILL.md +78 -0
- package/skills/trackops-quality-guard/agents/openai.yaml +7 -0
- package/skills/trackops-quality-guard/locales/en/SKILL.md +78 -0
- package/skills/trackops-quality-guard/locales/en/references/commands.md +36 -0
- package/skills/trackops-quality-guard/locales/en/references/decision-policy.md +16 -0
- package/skills/trackops-quality-guard/locales/en/references/output-format.md +24 -0
- package/skills/trackops-quality-guard/references/commands.md +36 -0
- package/skills/trackops-quality-guard/references/decision-policy.md +16 -0
- package/skills/trackops-quality-guard/references/output-format.md +24 -0
- package/skills/trackops-quality-guard/skill.json +28 -0
- package/templates/skills/opera-skill/SKILL.md +4 -0
- package/templates/skills/opera-skill/locales/en/SKILL.md +4 -0
- package/templates/skills/trackops-quality-guard/SKILL.md +72 -0
- package/templates/skills/trackops-quality-guard/locales/en/SKILL.md +72 -0
- package/templates/skills/trackops-quality-guard/locales/en/references/commands.md +30 -0
- package/templates/skills/trackops-quality-guard/locales/en/references/decision-policy.md +14 -0
- package/templates/skills/trackops-quality-guard/locales/en/references/output-format.md +21 -0
- package/templates/skills/trackops-quality-guard/references/commands.md +30 -0
- package/templates/skills/trackops-quality-guard/references/decision-policy.md +14 -0
- package/templates/skills/trackops-quality-guard/references/output-format.md +21 -0
- package/templates/skills/opera-quality-guard/SKILL.md +0 -26
- package/templates/skills/opera-quality-guard/locales/en/SKILL.md +0 -26
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ No sustituye tu producto. Ordena el trabajo que ocurre alrededor del producto:
|
|
|
31
31
|
- define una fuente de verdad operativa dentro del repo
|
|
32
32
|
- separa producto y operaciones para que no se mezclen
|
|
33
33
|
- da contexto persistente a los agentes entre sesiones
|
|
34
|
-
- expone estado, tareas,
|
|
34
|
+
- expone estado, tareas, Quality Guard, readiness y release desde CLI y dashboard
|
|
35
35
|
|
|
36
36
|
Si quieres ir más allá, puedes instalar OPERA, la metodología opcional que añade discovery, contrato operativo, readiness y un equipo coordinado de especialistas.
|
|
37
37
|
|
|
@@ -152,6 +152,12 @@ trackops dashboard
|
|
|
152
152
|
trackops quality status
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
+
Si quieres una skill pública centrada en esta capa de auditoría, verificación y readiness:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npx skills add Baxahaun/trackops --skill trackops-quality-guard --agent "*" --global -y
|
|
159
|
+
```
|
|
160
|
+
|
|
155
161
|
Para release:
|
|
156
162
|
|
|
157
163
|
```bash
|
|
@@ -181,7 +187,7 @@ It does not replace your product. It organizes the work around the product:
|
|
|
181
187
|
- defines an operational source of truth inside the repository
|
|
182
188
|
- keeps product and operations separate
|
|
183
189
|
- gives agents persistent context across sessions
|
|
184
|
-
- exposes status, tasks,
|
|
190
|
+
- exposes status, tasks, Quality Guard, readiness, and release state through CLI and dashboard
|
|
185
191
|
|
|
186
192
|
If you need a stricter operating model, you can install OPERA, the optional methodology that adds discovery, an operating contract, readiness checks, and a coordinated team of specialists.
|
|
187
193
|
|
|
@@ -302,6 +308,12 @@ trackops dashboard
|
|
|
302
308
|
trackops quality status
|
|
303
309
|
```
|
|
304
310
|
|
|
311
|
+
If you want a public skill focused on this audit, verification, and readiness layer:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
npx skills add Baxahaun/trackops --skill trackops-quality-guard --agent "*" --global -y
|
|
315
|
+
```
|
|
316
|
+
|
|
305
317
|
For release:
|
|
306
318
|
|
|
307
319
|
```bash
|
package/lib/opera.js
CHANGED
|
@@ -15,7 +15,7 @@ const fmt = require("./cli-format");
|
|
|
15
15
|
const TEMPLATES_DIR = path.join(__dirname, "..", "templates", "opera");
|
|
16
16
|
const SKILLS_TEMPLATES_DIR = path.join(__dirname, "..", "templates", "skills");
|
|
17
17
|
const OPERA_VERSION = require("../package.json").version;
|
|
18
|
-
const AUXILIARY_SKILLS = ["opera-skill", "
|
|
18
|
+
const AUXILIARY_SKILLS = ["opera-skill", "trackops-quality-guard", "project-starter-skill", "opera-contract-auditor", "opera-policy-guard"];
|
|
19
19
|
|
|
20
20
|
function nowIso() {
|
|
21
21
|
return new Date().toISOString();
|
package/lib/skills.js
CHANGED
|
@@ -8,15 +8,16 @@ const { t, setLocale } = require("./i18n");
|
|
|
8
8
|
const { resolveSkillFile } = require("./resources");
|
|
9
9
|
const fmt = require("./cli-format");
|
|
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
|
-
"
|
|
16
|
-
"opera-
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
11
|
+
const SKILLS_TEMPLATES_DIR = path.join(__dirname, "..", "templates", "skills");
|
|
12
|
+
const INSTALLED_SKILL_PRIORITY = [
|
|
13
|
+
"opera-skill",
|
|
14
|
+
"project-starter-skill",
|
|
15
|
+
"trackops-quality-guard",
|
|
16
|
+
"opera-contract-auditor",
|
|
17
|
+
"opera-policy-guard",
|
|
18
|
+
"commiter",
|
|
19
|
+
"changelog-updater",
|
|
20
|
+
];
|
|
20
21
|
|
|
21
22
|
function copyDirRecursive(src, dest) {
|
|
22
23
|
fs.mkdirSync(dest, { recursive: true });
|
|
@@ -31,20 +32,62 @@ function copyDirRecursive(src, dest) {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
35
|
+
function stripFrontmatterValue(value) {
|
|
36
|
+
return String(value || "").trim().replace(/^["']|["']$/g, "");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseFrontmatter(content) {
|
|
40
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
41
|
+
if (!match) return {};
|
|
42
|
+
const lines = match[1].split("\n");
|
|
43
|
+
const root = {};
|
|
44
|
+
const stack = [{ indent: -1, container: root }];
|
|
45
|
+
|
|
46
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
47
|
+
const line = lines[index].replace(/\t/g, " ");
|
|
48
|
+
const trimmed = line.trim();
|
|
49
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
50
|
+
const indent = line.match(/^ */)[0].length;
|
|
51
|
+
|
|
52
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
53
|
+
stack.pop();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const current = stack[stack.length - 1].container;
|
|
57
|
+
if (trimmed.startsWith("- ")) {
|
|
58
|
+
if (Array.isArray(current)) {
|
|
59
|
+
current.push(stripFrontmatterValue(trimmed.slice(2)));
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const sep = trimmed.indexOf(":");
|
|
65
|
+
if (sep <= 0 || Array.isArray(current)) continue;
|
|
66
|
+
|
|
67
|
+
const key = stripFrontmatterValue(trimmed.slice(0, sep));
|
|
68
|
+
const rawValue = trimmed.slice(sep + 1).trim();
|
|
69
|
+
if (rawValue) {
|
|
70
|
+
current[key] = stripFrontmatterValue(rawValue);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let child = {};
|
|
75
|
+
for (let peekIndex = index + 1; peekIndex < lines.length; peekIndex += 1) {
|
|
76
|
+
const peekLine = lines[peekIndex].replace(/\t/g, " ");
|
|
77
|
+
const peekTrimmed = peekLine.trim();
|
|
78
|
+
if (!peekTrimmed || peekTrimmed.startsWith("#")) continue;
|
|
79
|
+
const peekIndent = peekLine.match(/^ */)[0].length;
|
|
80
|
+
if (peekIndent <= indent) break;
|
|
81
|
+
child = peekTrimmed.startsWith("- ") ? [] : {};
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
current[key] = child;
|
|
86
|
+
stack.push({ indent, container: child });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return root;
|
|
90
|
+
}
|
|
48
91
|
|
|
49
92
|
function getSkillsDir(root) {
|
|
50
93
|
return config.ensureContext(root).paths.skillsDir;
|
|
@@ -54,29 +97,39 @@ function getRegistryPath(root) {
|
|
|
54
97
|
return config.ensureContext(root).paths.registryPath;
|
|
55
98
|
}
|
|
56
99
|
|
|
57
|
-
function catalogSkills() {
|
|
58
|
-
if (!fs.existsSync(SKILLS_TEMPLATES_DIR)) return [];
|
|
59
|
-
return fs.readdirSync(SKILLS_TEMPLATES_DIR, { withFileTypes: true })
|
|
60
|
-
.filter((e) => e.isDirectory())
|
|
61
|
-
.map((e) => {
|
|
62
|
-
const skillMd = path.join(SKILLS_TEMPLATES_DIR, e.name, "SKILL.md");
|
|
63
|
-
if (!fs.existsSync(skillMd)) return null;
|
|
64
|
-
const fm = parseFrontmatter(fs.readFileSync(skillMd, "utf8"));
|
|
65
|
-
return {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
100
|
+
function catalogSkills() {
|
|
101
|
+
if (!fs.existsSync(SKILLS_TEMPLATES_DIR)) return [];
|
|
102
|
+
return fs.readdirSync(SKILLS_TEMPLATES_DIR, { withFileTypes: true })
|
|
103
|
+
.filter((e) => e.isDirectory())
|
|
104
|
+
.map((e) => {
|
|
105
|
+
const skillMd = path.join(SKILLS_TEMPLATES_DIR, e.name, "SKILL.md");
|
|
106
|
+
if (!fs.existsSync(skillMd)) return null;
|
|
107
|
+
const fm = parseFrontmatter(fs.readFileSync(skillMd, "utf8"));
|
|
108
|
+
return {
|
|
109
|
+
name: e.name,
|
|
110
|
+
description: fm.description || "",
|
|
111
|
+
version: fm.metadata?.version || fm.version || "1.0",
|
|
112
|
+
triggers: Array.isArray(fm.metadata?.triggers) ? fm.metadata.triggers : [],
|
|
113
|
+
};
|
|
114
|
+
})
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
117
|
+
}
|
|
69
118
|
|
|
70
119
|
function installedSkills(root) {
|
|
71
120
|
const skillsDir = getSkillsDir(root);
|
|
72
121
|
if (!fs.existsSync(skillsDir)) return [];
|
|
73
122
|
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 {
|
|
78
|
-
|
|
79
|
-
|
|
123
|
+
.filter((e) => e.isDirectory() && fs.existsSync(path.join(skillsDir, e.name, "SKILL.md")))
|
|
124
|
+
.map((e) => {
|
|
125
|
+
const fm = parseFrontmatter(fs.readFileSync(path.join(skillsDir, e.name, "SKILL.md"), "utf8"));
|
|
126
|
+
return {
|
|
127
|
+
name: e.name,
|
|
128
|
+
description: fm.description || "",
|
|
129
|
+
version: fm.metadata?.version || fm.version || "1.0",
|
|
130
|
+
};
|
|
131
|
+
})
|
|
132
|
+
.filter(Boolean);
|
|
80
133
|
skills.sort((a, b) => {
|
|
81
134
|
const ai = INSTALLED_SKILL_PRIORITY.indexOf(a.name);
|
|
82
135
|
const bi = INSTALLED_SKILL_PRIORITY.indexOf(b.name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackops",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Local control and coordination system for AI-agent software development",
|
|
5
5
|
"main": "lib/control.js",
|
|
6
6
|
"bin": {
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=18"
|
|
51
51
|
},
|
|
52
|
-
"scripts": {
|
|
53
|
-
"postinstall": "node scripts/postinstall-locale.js",
|
|
54
|
-
"test": "node --test scripts/quality-unit-tests.js && node scripts/smoke-tests.js",
|
|
55
|
-
"test:unit": "node --test scripts/quality-unit-tests.js",
|
|
56
|
-
"test:smoke": "node scripts/smoke-tests.js",
|
|
52
|
+
"scripts": {
|
|
53
|
+
"postinstall": "node scripts/postinstall-locale.js",
|
|
54
|
+
"test": "node --test scripts/quality-unit-tests.js && node scripts/smoke-tests.js",
|
|
55
|
+
"test:unit": "node --test scripts/quality-unit-tests.js",
|
|
56
|
+
"test:smoke": "node scripts/smoke-tests.js",
|
|
57
57
|
"skill:sync-version": "node scripts/sync-skill-version.js",
|
|
58
58
|
"skill:validate": "node scripts/validate-skill.js",
|
|
59
59
|
"skill:smoke": "node scripts/skills-marketplace-smoke.js",
|
|
@@ -1,124 +1,156 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const assert = require("assert");
|
|
4
|
-
const fs = require("fs");
|
|
5
|
-
const os = require("os");
|
|
6
|
-
const path = require("path");
|
|
7
|
-
const { spawnSync } = require("child_process");
|
|
8
|
-
|
|
9
|
-
const ROOT = path.resolve(__dirname, "..");
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const assert = require("assert");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const { spawnSync } = require("child_process");
|
|
8
|
+
|
|
9
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
10
|
+
const SKILLS_ROOT = path.join(ROOT, "skills");
|
|
11
|
+
const REQUIRED_PUBLIC_SKILLS = ["trackops", "trackops-quality-guard"];
|
|
12
|
+
|
|
13
|
+
const REQUIRED_FILES_BY_SKILL = {
|
|
14
|
+
trackops: [
|
|
15
|
+
"references/activation.md",
|
|
16
|
+
"references/workflow.md",
|
|
17
|
+
"references/troubleshooting.md",
|
|
18
|
+
"skill.json",
|
|
19
|
+
],
|
|
20
|
+
"trackops-quality-guard": [
|
|
21
|
+
"references/commands.md",
|
|
22
|
+
"references/decision-policy.md",
|
|
23
|
+
"references/output-format.md",
|
|
24
|
+
"skill.json",
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function run(command, args, cwd, envOverrides = {}) {
|
|
29
|
+
const shell = process.platform === "win32" && /\.(cmd|bat)$/i.test(command);
|
|
30
|
+
return spawnSync(command, args, {
|
|
31
|
+
cwd,
|
|
32
|
+
encoding: "utf8",
|
|
33
|
+
env: { ...process.env, ...envOverrides },
|
|
34
|
+
shell,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getNpxCommand() {
|
|
39
|
+
return process.platform === "win32" ? "npx.cmd" : "npx";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ensureOk(result, context) {
|
|
43
|
+
const output = `${result.stdout || ""}\n${result.stderr || ""}`.trim();
|
|
44
|
+
assert.strictEqual(result.status, 0, output || context);
|
|
45
|
+
return output;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function initGitRepo(repo) {
|
|
49
|
+
ensureOk(run("git", ["init"], repo), "git init failed");
|
|
50
|
+
ensureOk(run("git", ["config", "user.email", "skills-smoke@example.com"], repo), "git config email failed");
|
|
51
|
+
ensureOk(run("git", ["config", "user.name", "Skills Smoke"], repo), "git config name failed");
|
|
52
|
+
ensureOk(run("git", ["add", "."], repo), "git add failed");
|
|
53
|
+
ensureOk(run("git", ["commit", "-m", "skills smoke fixture"], repo), "git commit failed");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildIsolatedEnv(homeRoot) {
|
|
57
|
+
const env = {
|
|
58
|
+
HOME: homeRoot,
|
|
59
|
+
USERPROFILE: homeRoot,
|
|
60
|
+
APPDATA: path.join(homeRoot, "AppData", "Roaming"),
|
|
61
|
+
LOCALAPPDATA: path.join(homeRoot, "AppData", "Local"),
|
|
62
|
+
XDG_CONFIG_HOME: path.join(homeRoot, ".config"),
|
|
63
|
+
};
|
|
64
|
+
for (const value of Object.values(env)) {
|
|
65
|
+
fs.mkdirSync(value, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
return env;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function findInstalledSkill(rootDir, skillName) {
|
|
71
|
+
const matches = [];
|
|
72
|
+
|
|
73
|
+
function walk(dir) {
|
|
74
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
const fullPath = path.join(dir, entry.name);
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
walk(fullPath);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (entry.isFile() && entry.name === "SKILL.md" && path.basename(path.dirname(fullPath)) === skillName) {
|
|
82
|
+
matches.push(path.dirname(fullPath));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
walk(rootDir);
|
|
88
|
+
return matches;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function discoverPublicSkills() {
|
|
92
|
+
return fs.readdirSync(SKILLS_ROOT, { withFileTypes: true })
|
|
93
|
+
.filter((entry) => entry.isDirectory() && fs.existsSync(path.join(SKILLS_ROOT, entry.name, "skill.json")))
|
|
94
|
+
.map((entry) => {
|
|
95
|
+
const dir = path.join(SKILLS_ROOT, entry.name);
|
|
96
|
+
const config = JSON.parse(fs.readFileSync(path.join(dir, "skill.json"), "utf8"));
|
|
97
|
+
return { name: entry.name, dir, config };
|
|
98
|
+
})
|
|
99
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function main() {
|
|
103
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "trackops-skills-smoke-"));
|
|
104
|
+
const sourceRepo = path.join(tempRoot, "source");
|
|
105
|
+
const homeRoot = path.join(tempRoot, "home");
|
|
106
|
+
|
|
107
|
+
fs.mkdirSync(sourceRepo, { recursive: true });
|
|
108
|
+
fs.cpSync(SKILLS_ROOT, path.join(sourceRepo, "skills"), { recursive: true });
|
|
109
|
+
initGitRepo(sourceRepo);
|
|
110
|
+
|
|
111
|
+
const env = buildIsolatedEnv(homeRoot);
|
|
112
|
+
const skills = discoverPublicSkills();
|
|
113
|
+
for (const skillName of REQUIRED_PUBLIC_SKILLS) {
|
|
114
|
+
assert.ok(skills.some((entry) => entry.name === skillName), `missing public skill ${skillName}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const entry of skills) {
|
|
118
|
+
const distribution = entry.config.distribution || {};
|
|
119
|
+
const skillName = distribution.skill || entry.config.name;
|
|
120
|
+
|
|
121
|
+
const listResult = run(
|
|
122
|
+
getNpxCommand(),
|
|
123
|
+
["--yes", "skills", "add", sourceRepo, "--list", "--skill", skillName, "--full-depth", "-y"],
|
|
124
|
+
ROOT,
|
|
125
|
+
env,
|
|
126
|
+
);
|
|
127
|
+
const listOutput = ensureOk(listResult, `skills list failed for ${skillName}`);
|
|
128
|
+
assert.ok(listOutput.includes(skillName), listOutput);
|
|
129
|
+
|
|
130
|
+
const installResult = run(
|
|
131
|
+
getNpxCommand(),
|
|
132
|
+
["--yes", "skills", "add", sourceRepo, "--skill", skillName, "--full-depth", "--global", "--agent", "codex", "--copy", "-y"],
|
|
133
|
+
ROOT,
|
|
134
|
+
env,
|
|
135
|
+
);
|
|
136
|
+
ensureOk(installResult, `skills install failed for ${skillName}`);
|
|
137
|
+
|
|
138
|
+
const installed = findInstalledSkill(homeRoot, skillName);
|
|
139
|
+
assert.ok(installed.length >= 1, `${skillName} was not installed under ${homeRoot}`);
|
|
140
|
+
|
|
141
|
+
const installedSkillDir = installed[0];
|
|
142
|
+
for (const relativeFile of REQUIRED_FILES_BY_SKILL[skillName] || []) {
|
|
143
|
+
assert.ok(fs.existsSync(path.join(installedSkillDir, relativeFile)), `missing ${relativeFile} for ${skillName}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
148
|
+
console.log("skills marketplace smoke OK");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
main();
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(error.message);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
package/scripts/smoke-tests.js
CHANGED
|
@@ -340,13 +340,18 @@ async function main() {
|
|
|
340
340
|
assert.ok(fs.existsSync(path.join(nonEmptyProject, "app", "package.json")));
|
|
341
341
|
assert.ok(fs.existsSync(path.join(nonEmptyProject, "ops", "project_control.json")));
|
|
342
342
|
|
|
343
|
-
writeJson(path.join(splitProject, "app", "package.json"), {
|
|
344
|
-
name: "split-demo",
|
|
345
|
-
version: "1.0.0",
|
|
346
|
-
dependencies: { openai: "^4.0.0" },
|
|
347
|
-
scripts: { test: "echo ok" },
|
|
348
|
-
});
|
|
349
|
-
runNode([BIN, "
|
|
343
|
+
writeJson(path.join(splitProject, "app", "package.json"), {
|
|
344
|
+
name: "split-demo",
|
|
345
|
+
version: "1.0.0",
|
|
346
|
+
dependencies: { openai: "^4.0.0" },
|
|
347
|
+
scripts: { test: "echo ok" },
|
|
348
|
+
});
|
|
349
|
+
const skillCatalog = runNode([BIN, "skill", "catalog"], splitProject);
|
|
350
|
+
assert.match(skillCatalog, /trackops-quality-guard/);
|
|
351
|
+
assert.doesNotMatch(skillCatalog, /opera-quality-guard/);
|
|
352
|
+
runNode([BIN, "skill", "install", "trackops-quality-guard"], splitProject);
|
|
353
|
+
assert.ok(fs.existsSync(path.join(splitProject, "ops", ".agents", "skills", "trackops-quality-guard", "SKILL.md")));
|
|
354
|
+
runNode([BIN, "opera", "install", "--locale", "en", "--non-interactive"], splitProject);
|
|
350
355
|
|
|
351
356
|
assert.ok(fs.existsSync(path.join(splitProject, "ops", "genesis.md")));
|
|
352
357
|
assert.ok(fs.existsSync(path.join(splitProject, "ops", ".agent", "hub", "agent.md")));
|
|
@@ -367,13 +372,15 @@ async function main() {
|
|
|
367
372
|
assert.strictEqual(operaControl.meta.opera.installed, true);
|
|
368
373
|
assert.strictEqual(operaControl.meta.opera.bootstrap.mode, "agent_handoff");
|
|
369
374
|
assert.strictEqual(operaControl.meta.opera.bootstrap.status, "awaiting_agent");
|
|
370
|
-
assert.ok(operaControl.meta.opera.skills.includes("opera-skill"));
|
|
371
|
-
assert.ok(operaControl.meta.opera.skills.includes("
|
|
372
|
-
assert.ok(operaControl.meta.opera.skills.includes("
|
|
373
|
-
assert.ok(operaControl.meta.opera.skills.includes("opera-
|
|
374
|
-
assert.ok(
|
|
375
|
-
assert.ok(fs.existsSync(path.join(splitProject, "ops", ".agents", "skills", "opera-skill", "
|
|
376
|
-
assert.ok(fs.existsSync(path.join(splitProject, "ops", ".agents", "skills", "
|
|
375
|
+
assert.ok(operaControl.meta.opera.skills.includes("opera-skill"));
|
|
376
|
+
assert.ok(operaControl.meta.opera.skills.includes("trackops-quality-guard"));
|
|
377
|
+
assert.ok(operaControl.meta.opera.skills.includes("project-starter-skill"));
|
|
378
|
+
assert.ok(operaControl.meta.opera.skills.includes("opera-contract-auditor"));
|
|
379
|
+
assert.ok(operaControl.meta.opera.skills.includes("opera-policy-guard"));
|
|
380
|
+
assert.ok(fs.existsSync(path.join(splitProject, "ops", ".agents", "skills", "opera-skill", "SKILL.md")));
|
|
381
|
+
assert.ok(fs.existsSync(path.join(splitProject, "ops", ".agents", "skills", "trackops-quality-guard", "SKILL.md")));
|
|
382
|
+
assert.ok(fs.existsSync(path.join(splitProject, "ops", ".agents", "skills", "opera-skill", "references", "phase-dod.md")));
|
|
383
|
+
assert.ok(fs.existsSync(path.join(splitProject, "ops", ".agents", "skills", "project-starter-skill", "references", "opera-cycle.md")));
|
|
377
384
|
assert.ok(operaControl.meta.environment.requiredKeys.includes("OPENAI_API_KEY"));
|
|
378
385
|
const envRootText = fs.readFileSync(path.join(splitProject, ".env"), "utf8");
|
|
379
386
|
assert.match(envRootText, /OPENAI_API_KEY=/);
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const path = require("path");
|
|
5
|
-
|
|
6
|
-
const ROOT = path.resolve(__dirname, "..");
|
|
7
|
-
const PACKAGE_FILE = path.join(ROOT, "package.json");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
7
|
+
const PACKAGE_FILE = path.join(ROOT, "package.json");
|
|
8
|
+
|
|
9
|
+
function getSkillFiles() {
|
|
10
|
+
const skillsRoot = path.join(ROOT, "skills");
|
|
11
|
+
if (!fs.existsSync(skillsRoot)) return [];
|
|
12
|
+
return fs.readdirSync(skillsRoot, { withFileTypes: true })
|
|
13
|
+
.filter((entry) => entry.isDirectory())
|
|
14
|
+
.map((entry) => path.join(skillsRoot, entry.name, "skill.json"))
|
|
15
|
+
.filter((filePath) => fs.existsSync(filePath))
|
|
16
|
+
.sort((a, b) => a.localeCompare(b));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function main() {
|
|
20
|
+
const pkg = JSON.parse(fs.readFileSync(PACKAGE_FILE, "utf8"));
|
|
21
|
+
const skillFiles = getSkillFiles();
|
|
22
|
+
for (const skillFile of skillFiles) {
|
|
23
|
+
const skill = JSON.parse(fs.readFileSync(skillFile, "utf8"));
|
|
24
|
+
skill.skillVersion = pkg.version;
|
|
25
|
+
skill.trackopsVersion = pkg.version;
|
|
26
|
+
fs.writeFileSync(skillFile, `${JSON.stringify(skill, null, 2)}\n`, "utf8");
|
|
27
|
+
console.log(`Synced ${path.relative(ROOT, skillFile)} to version ${pkg.version}.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
main();
|