trackops 2.0.5 → 2.1.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 +319 -695
- package/bin/trackops.js +52 -23
- package/lib/cli-format.js +118 -0
- package/lib/config.js +277 -44
- package/lib/control.js +1052 -352
- package/lib/env.js +40 -28
- package/lib/i18n.js +5 -4
- package/lib/init.js +194 -56
- package/lib/opera-bootstrap.js +326 -106
- package/lib/opera-phase-dod.js +485 -0
- package/lib/opera.js +243 -78
- package/lib/plans.js +1329 -0
- package/lib/quality-assert.js +49 -0
- package/lib/quality.js +1759 -0
- package/lib/release.js +18 -11
- package/lib/server.js +504 -192
- package/lib/skills.js +43 -35
- package/lib/workspace.js +32 -21
- package/locales/en.json +431 -75
- package/locales/es.json +432 -76
- package/package.json +6 -5
- package/scripts/quality-unit-tests.js +130 -0
- package/scripts/smoke-tests.js +438 -96
- package/skills/trackops/skill.json +29 -29
- package/templates/skills/opera-quality-guard/SKILL.md +26 -0
- package/templates/skills/opera-quality-guard/locales/en/SKILL.md +26 -0
- package/templates/skills/opera-skill/SKILL.md +8 -0
- package/templates/skills/opera-skill/locales/en/SKILL.md +8 -0
- package/ui/js/api.js +93 -26
- package/ui/js/app.js +13 -7
- package/ui/js/filters.js +49 -29
- package/ui/js/time-tracker.js +41 -28
- package/ui/js/views/board.js +22 -14
- package/ui/js/views/dashboard.js +206 -49
- package/ui/js/views/execution.js +7 -3
- package/ui/js/views/plans.js +284 -0
- package/ui/js/views/scrum.js +25 -13
- package/ui/js/views/sidebar.js +9 -8
- package/ui/js/views/tasks.js +238 -134
|
@@ -0,0 +1,130 @@
|
|
|
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
|
+
const test = require("node:test");
|
|
9
|
+
|
|
10
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
11
|
+
const BIN = path.join(ROOT, "bin", "trackops.js");
|
|
12
|
+
|
|
13
|
+
function runNode(args, cwd, envOverrides = {}) {
|
|
14
|
+
const result = spawnSync(process.execPath, args, {
|
|
15
|
+
cwd,
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
env: { ...process.env, ...envOverrides },
|
|
18
|
+
});
|
|
19
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout || `fallo ejecutando ${args.join(" ")}`);
|
|
20
|
+
return result.stdout;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function runNodeResult(args, cwd, envOverrides = {}) {
|
|
24
|
+
return spawnSync(process.execPath, args, {
|
|
25
|
+
cwd,
|
|
26
|
+
encoding: "utf8",
|
|
27
|
+
env: { ...process.env, ...envOverrides },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function writeJson(filePath, data) {
|
|
32
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
33
|
+
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createProject(name) {
|
|
37
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `trackops-quality-${name}-`));
|
|
38
|
+
runNode([BIN, "init", "--locale", "en"], dir);
|
|
39
|
+
return dir;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readProbe(projectRoot, probeId) {
|
|
43
|
+
const output = runNode([BIN, "quality", "status", "--json"], projectRoot);
|
|
44
|
+
const report = JSON.parse(output);
|
|
45
|
+
return report.probes.find((probe) => probe.id === probeId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test("quality read-only commands do not create ops/quality storage", () => {
|
|
49
|
+
const projectRoot = createProject("readonly");
|
|
50
|
+
const qualityDir = path.join(projectRoot, "ops", "quality");
|
|
51
|
+
|
|
52
|
+
const commands = [
|
|
53
|
+
[BIN, "quality", "status", "--json"],
|
|
54
|
+
[BIN, "quality", "phase-readiness", "--json"],
|
|
55
|
+
[BIN, "quality", "release-readiness", "--json"],
|
|
56
|
+
[BIN, "quality", "promote-readiness", "--target", "production", "--json"],
|
|
57
|
+
[BIN, "quality", "waiver", "list", "--json"],
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
for (const command of commands) {
|
|
61
|
+
runNodeResult(command, projectRoot);
|
|
62
|
+
assert.ok(!fs.existsSync(qualityDir), `el comando ${command.join(" ")} no debe crear ops/quality`);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("quality verify build without commands fails with not_configured", () => {
|
|
67
|
+
const projectRoot = createProject("verify-build");
|
|
68
|
+
writeJson(path.join(projectRoot, "app", "package.json"), {
|
|
69
|
+
name: "verify-build",
|
|
70
|
+
version: "1.0.0",
|
|
71
|
+
scripts: {
|
|
72
|
+
test: `node -e "console.log('test ok')"`,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const result = runNodeResult([BIN, "quality", "verify", "--scope", "build", "--json"], projectRoot);
|
|
77
|
+
assert.notStrictEqual(result.status, 0);
|
|
78
|
+
const payload = JSON.parse(result.stdout);
|
|
79
|
+
assert.strictEqual(payload.status, "failed");
|
|
80
|
+
assert.strictEqual(payload.results[0].scope, "build");
|
|
81
|
+
assert.strictEqual(payload.results[0].status, "not_configured");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("quality verify smoke uses app package cwd when inferred from release:check", () => {
|
|
85
|
+
const projectRoot = createProject("verify-smoke");
|
|
86
|
+
writeJson(path.join(projectRoot, "app", "package.json"), {
|
|
87
|
+
name: "verify-smoke",
|
|
88
|
+
version: "1.0.0",
|
|
89
|
+
scripts: {
|
|
90
|
+
"release:check": `node -e "console.log(process.cwd())"`,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const result = runNodeResult([BIN, "quality", "verify", "--scope", "smoke", "--json"], projectRoot);
|
|
95
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
96
|
+
const payload = JSON.parse(result.stdout);
|
|
97
|
+
assert.strictEqual(payload.status, "passed");
|
|
98
|
+
assert.strictEqual(payload.results[0].scope, "smoke");
|
|
99
|
+
assert.strictEqual(payload.results[0].status, "passed");
|
|
100
|
+
assert.strictEqual(payload.results[0].commands[0].cwd, path.join(projectRoot, "app"));
|
|
101
|
+
assert.match(payload.results[0].commands[0].stdout, /trackops-quality-verify-smoke-.*[\\/]app/);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("phase-readiness is advisory and returns structured DoD checks", () => {
|
|
105
|
+
const projectRoot = createProject("phase-readiness");
|
|
106
|
+
runNode([BIN, "opera", "install", "--locale", "en", "--no-bootstrap"], projectRoot);
|
|
107
|
+
|
|
108
|
+
const result = runNodeResult([BIN, "quality", "phase-readiness", "--json"], projectRoot);
|
|
109
|
+
assert.strictEqual(result.status, 0);
|
|
110
|
+
const payload = JSON.parse(result.stdout);
|
|
111
|
+
assert.strictEqual(payload.kind, "phase");
|
|
112
|
+
assert.ok(payload.definition);
|
|
113
|
+
assert.ok(Array.isArray(payload.checks));
|
|
114
|
+
assert.ok(payload.checks.length > 0);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("env.bridge-sync reports pass and mismatch correctly", () => {
|
|
118
|
+
const projectRoot = createProject("bridge-sync");
|
|
119
|
+
fs.writeFileSync(path.join(projectRoot, ".env"), "OPENAI_API_KEY=test\n", "utf8");
|
|
120
|
+
fs.writeFileSync(path.join(projectRoot, ".env.example"), "OPENAI_API_KEY=\n", "utf8");
|
|
121
|
+
runNode([BIN, "env", "sync"], projectRoot);
|
|
122
|
+
|
|
123
|
+
const okProbe = readProbe(projectRoot, "env.bridge-sync");
|
|
124
|
+
assert.strictEqual(okProbe.status, "pass");
|
|
125
|
+
|
|
126
|
+
fs.rmSync(path.join(projectRoot, "app", ".env"), { force: true });
|
|
127
|
+
fs.writeFileSync(path.join(projectRoot, "app", ".env"), "OPENAI_API_KEY=broken\n", "utf8");
|
|
128
|
+
const mismatchProbe = readProbe(projectRoot, "env.bridge-sync");
|
|
129
|
+
assert.strictEqual(mismatchProbe.status, "fail");
|
|
130
|
+
});
|