trackops 2.0.5 → 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.
- package/README.md +91 -61
- package/bin/trackops.js +28 -7
- package/lib/cli-format.js +118 -0
- package/lib/config.js +29 -3
- package/lib/control.js +278 -116
- package/lib/env.js +40 -28
- package/lib/i18n.js +5 -4
- package/lib/init.js +149 -41
- package/lib/opera-bootstrap.js +251 -71
- package/lib/opera.js +235 -73
- package/lib/skills.js +43 -35
- package/lib/workspace.js +32 -21
- package/locales/en.json +183 -61
- package/locales/es.json +184 -62
- package/package.json +1 -1
- package/scripts/smoke-tests.js +81 -39
- package/skills/trackops/skill.json +2 -2
package/package.json
CHANGED
package/scripts/smoke-tests.js
CHANGED
|
@@ -26,13 +26,24 @@ function runNode(args, cwd, envOverrides = {}) {
|
|
|
26
26
|
return result.stdout;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function runNodeResult(args, cwd, envOverrides = {}) {
|
|
30
|
-
return spawnSync(process.execPath, args, {
|
|
31
|
-
cwd,
|
|
32
|
-
encoding: "utf8",
|
|
33
|
-
env: { ...process.env, ...envOverrides },
|
|
34
|
-
});
|
|
35
|
-
}
|
|
29
|
+
function runNodeResult(args, cwd, envOverrides = {}) {
|
|
30
|
+
return spawnSync(process.execPath, args, {
|
|
31
|
+
cwd,
|
|
32
|
+
encoding: "utf8",
|
|
33
|
+
env: { ...process.env, ...envOverrides },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function runNodeWithInput(args, cwd, input, envOverrides = {}) {
|
|
38
|
+
const result = spawnSync(process.execPath, args, {
|
|
39
|
+
cwd,
|
|
40
|
+
encoding: "utf8",
|
|
41
|
+
input,
|
|
42
|
+
env: { ...process.env, ...envOverrides },
|
|
43
|
+
});
|
|
44
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout || `fallo ejecutando ${args.join(" ")}`);
|
|
45
|
+
return result.stdout;
|
|
46
|
+
}
|
|
36
47
|
|
|
37
48
|
function runCommand(command, args, cwd, envOverrides = {}) {
|
|
38
49
|
return spawnSync(command, args, {
|
|
@@ -232,11 +243,12 @@ async function main() {
|
|
|
232
243
|
const installedVersion = runNode([installedCli, "--version"], tempRoot);
|
|
233
244
|
assert.strictEqual(installedVersion.trim(), packageVersion);
|
|
234
245
|
|
|
235
|
-
const helpOutput = runNode([BIN, "help"], ROOT);
|
|
236
|
-
assert.doesNotMatch(helpOutput, /\btrackops agent\b/i);
|
|
237
|
-
assert.match(helpOutput, /workspace status\|migrate/i);
|
|
238
|
-
assert.match(helpOutput, /env status\|sync/i);
|
|
239
|
-
assert.match(helpOutput, /release \[--push\]/i);
|
|
246
|
+
const helpOutput = runNode([BIN, "help"], ROOT);
|
|
247
|
+
assert.doesNotMatch(helpOutput, /\btrackops agent\b/i);
|
|
248
|
+
assert.match(helpOutput, /workspace status\|migrate/i);
|
|
249
|
+
assert.match(helpOutput, /env status\|sync/i);
|
|
250
|
+
assert.match(helpOutput, /release \[--push\]/i);
|
|
251
|
+
assert.match(helpOutput, /--plain|--a11y/i);
|
|
240
252
|
|
|
241
253
|
const versionOutput = runNode([BIN, "--version"], ROOT);
|
|
242
254
|
assert.strictEqual(versionOutput.trim(), packageVersion);
|
|
@@ -270,31 +282,38 @@ async function main() {
|
|
|
270
282
|
assert.match(projectLocaleDoctor, /Project language: es|Idioma del proyecto: es/);
|
|
271
283
|
assert.match(projectLocaleDoctor, /Source: project|Origen: proyecto/);
|
|
272
284
|
|
|
273
|
-
const statusFromWorkspace = runNode([BIN, "status"], splitProject);
|
|
274
|
-
const statusFromApp = runNode([BIN, "status"], path.join(splitProject, "app"));
|
|
275
|
-
const statusFromOps = runNode([BIN, "status"], path.join(splitProject, "ops"));
|
|
276
|
-
assert.match(statusFromWorkspace, /Layout: split/);
|
|
277
|
-
assert.match(statusFromApp, /Layout: split/);
|
|
278
|
-
assert.match(statusFromOps, /Layout: split/);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
285
|
+
const statusFromWorkspace = runNode([BIN, "status"], splitProject);
|
|
286
|
+
const statusFromApp = runNode([BIN, "status"], path.join(splitProject, "app"));
|
|
287
|
+
const statusFromOps = runNode([BIN, "status"], path.join(splitProject, "ops"));
|
|
288
|
+
assert.match(statusFromWorkspace, /Layout: split|Estructura: split/);
|
|
289
|
+
assert.match(statusFromApp, /Layout: split|Estructura: split/);
|
|
290
|
+
assert.match(statusFromOps, /Layout: split|Estructura: split/);
|
|
291
|
+
assert.match(statusFromWorkspace, /Git: not initialized|Git: no inicializado/i);
|
|
292
|
+
assert.doesNotMatch(statusFromWorkspace, /Branch: detached|Rama: detached/i);
|
|
293
|
+
|
|
294
|
+
const nextOutput = runNode([BIN, "next"], splitProject);
|
|
295
|
+
assert.match(nextOutput, /ops-bootstrap/);
|
|
296
|
+
|
|
297
|
+
const rerunInit = runNode([BIN, "init", "--locale", "es"], splitProject, { TRACKOPS_BOOTSTRAP_HOME: bootstrapHome });
|
|
298
|
+
assert.match(rerunInit, /Updated \.trackops-workspace\.json|Actualizado \.trackops-workspace\.json/);
|
|
299
|
+
|
|
300
|
+
runNode([BIN, "sync"], splitProject);
|
|
284
301
|
for (const file of ["task_plan.md", "progress.md", "findings.md"]) {
|
|
285
302
|
assert.ok(fs.existsSync(path.join(splitProject, "ops", file)), `${file} no fue generado en ops/`);
|
|
286
303
|
}
|
|
287
|
-
|
|
288
|
-
const envStatus = runNode([BIN, "env", "status"], splitProject);
|
|
289
|
-
assert.match(envStatus, /Root \.env:/);
|
|
290
|
-
assert.match(envStatus, /App bridge:/);
|
|
291
|
-
|
|
292
|
-
const nonEmptyProject = path.join(tempRoot, "non-empty");
|
|
293
|
-
fs.mkdirSync(nonEmptyProject, { recursive: true });
|
|
294
|
-
writeJson(path.join(nonEmptyProject, "package.json"), { name: "existing-app", version: "1.0.0" });
|
|
295
|
-
const initNonEmpty =
|
|
296
|
-
assert.
|
|
297
|
-
assert.
|
|
304
|
+
|
|
305
|
+
const envStatus = runNode([BIN, "env", "status"], splitProject);
|
|
306
|
+
assert.match(envStatus, /Root \.env:|\.env raiz:/);
|
|
307
|
+
assert.match(envStatus, /App bridge:|Puente app:/);
|
|
308
|
+
|
|
309
|
+
const nonEmptyProject = path.join(tempRoot, "non-empty");
|
|
310
|
+
fs.mkdirSync(nonEmptyProject, { recursive: true });
|
|
311
|
+
writeJson(path.join(nonEmptyProject, "package.json"), { name: "existing-app", version: "1.0.0" });
|
|
312
|
+
const initNonEmpty = runNode([BIN, "init", "--locale", "en"], nonEmptyProject, { TRACKOPS_BOOTSTRAP_HOME: bootstrapHome });
|
|
313
|
+
assert.match(initNonEmpty, /adopted into app\/|movio a app\//i);
|
|
314
|
+
assert.ok(fs.existsSync(path.join(nonEmptyProject, ".trackops-workspace.json")));
|
|
315
|
+
assert.ok(fs.existsSync(path.join(nonEmptyProject, "app", "package.json")));
|
|
316
|
+
assert.ok(fs.existsSync(path.join(nonEmptyProject, "ops", "project_control.json")));
|
|
298
317
|
|
|
299
318
|
writeJson(path.join(splitProject, "app", "package.json"), {
|
|
300
319
|
name: "split-demo",
|
|
@@ -361,9 +380,32 @@ async function main() {
|
|
|
361
380
|
"--decision-ownership",
|
|
362
381
|
"user",
|
|
363
382
|
], directProject, { TRACKOPS_BOOTSTRAP_HOME: bootstrapHome });
|
|
364
|
-
const directControl = readJson(path.join(directProject, "ops", "project_control.json"));
|
|
365
|
-
assert.strictEqual(directControl.meta.opera.bootstrap.mode, "direct_cli");
|
|
366
|
-
assert.strictEqual(directControl.meta.opera.bootstrap.status, "awaiting_intake");
|
|
383
|
+
const directControl = readJson(path.join(directProject, "ops", "project_control.json"));
|
|
384
|
+
assert.strictEqual(directControl.meta.opera.bootstrap.mode, "direct_cli");
|
|
385
|
+
assert.strictEqual(directControl.meta.opera.bootstrap.status, "awaiting_intake");
|
|
386
|
+
assert.ok(fs.existsSync(path.join(directProject, "ops", "bootstrap", "intake.json")));
|
|
387
|
+
assert.ok(fs.existsSync(path.join(directProject, "ops", "bootstrap", "spec-dossier.md")));
|
|
388
|
+
assert.ok(fs.existsSync(path.join(directProject, "ops", "bootstrap", "open-questions.md")));
|
|
389
|
+
assert.ok(fs.existsSync(path.join(directProject, "ops", "bootstrap", "quality-report.json")));
|
|
390
|
+
const directOperaStatus = runNode([BIN, "opera", "status"], directProject, { TRACKOPS_BOOTSTRAP_HOME: bootstrapHome });
|
|
391
|
+
assert.match(directOperaStatus, /Intake:|Intake:/);
|
|
392
|
+
assert.match(directOperaStatus, /Spec dossier:|Specification brief:|Dossier de especificacion:/i);
|
|
393
|
+
assert.doesNotMatch(directOperaStatus, /The agent did not include|El agente no incluyo/i);
|
|
394
|
+
assert.doesNotMatch(directOperaStatus, /Handoff: ops[\\/]+bootstrap[\\/]+agent-handoff\.md/i);
|
|
395
|
+
const directHandoffSummary = runNode([BIN, "opera", "handoff"], directProject, { TRACKOPS_BOOTSTRAP_HOME: bootstrapHome });
|
|
396
|
+
assert.match(directHandoffSummary, /Guided bootstrap summary|Resumen del bootstrap guiado/i);
|
|
397
|
+
assert.doesNotMatch(directHandoffSummary, /Markdown handoff/i);
|
|
398
|
+
|
|
399
|
+
const plainStatus = runNode([BIN, "--plain", "status"], directProject, { TRACKOPS_BOOTSTRAP_HOME: bootstrapHome });
|
|
400
|
+
assert.match(plainStatus, /\[BLOCKED\]|\[PENDING\]/);
|
|
401
|
+
assert.doesNotMatch(plainStatus, /\u2500|\u23F3|\u26D4|\u2705/);
|
|
402
|
+
|
|
403
|
+
const promptProject = path.join(tempRoot, "prompt-demo");
|
|
404
|
+
fs.mkdirSync(promptProject, { recursive: true });
|
|
405
|
+
runNode([BIN, "init", "--locale", "en"], promptProject, { TRACKOPS_BOOTSTRAP_HOME: bootstrapHome });
|
|
406
|
+
writeJson(path.join(promptProject, "app", "package.json"), { name: "prompt-demo", version: "1.0.0" });
|
|
407
|
+
runNodeWithInput([BIN, "opera", "install", "--locale", "en"], promptProject, "\n", { TRACKOPS_BOOTSTRAP_HOME: bootstrapHome });
|
|
408
|
+
assert.ok(!fs.existsSync(path.join(promptProject, "ops", "bootstrap", "agent-handoff.md")));
|
|
367
409
|
|
|
368
410
|
writeJson(path.join(splitProject, "ops", "bootstrap", "intake.json"), {
|
|
369
411
|
version: 1,
|
|
@@ -522,8 +564,8 @@ async function main() {
|
|
|
522
564
|
version: "0.9.0",
|
|
523
565
|
};
|
|
524
566
|
writeJson(legacyUnsupportedControlPath, legacyUnsupportedControl);
|
|
525
|
-
const legacyStatusOutput = runNode([BIN, "opera", "status"], legacyUnsupportedProject, { TRACKOPS_BOOTSTRAP_HOME: bootstrapHome });
|
|
526
|
-
assert.match(legacyStatusOutput, /legacy_unsupported/);
|
|
567
|
+
const legacyStatusOutput = runNode([BIN, "opera", "status"], legacyUnsupportedProject, { TRACKOPS_BOOTSTRAP_HOME: bootstrapHome });
|
|
568
|
+
assert.match(legacyStatusOutput, /legacy_unsupported/);
|
|
527
569
|
const upgradeWithoutReset = runNodeResult([BIN, "opera", "upgrade", "--stable"], legacyUnsupportedProject, { TRACKOPS_BOOTSTRAP_HOME: bootstrapHome });
|
|
528
570
|
assert.strictEqual(upgradeWithoutReset.status, 0);
|
|
529
571
|
assert.match(`${upgradeWithoutReset.stdout}\n${upgradeWithoutReset.stderr}`, /legacy/i);
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"name": "trackops",
|
|
3
3
|
"shortDescription": "Global TrackOps skill that explains TrackOps, requires explicit runtime install, and guides per-repository activation.",
|
|
4
4
|
"description": "Explains what TrackOps does, installs the global skill layer, requires explicit runtime installation with npm, supports Spanish and English, activates TrackOps repository by repository, and routes OPERA onboarding into either direct bootstrap or agent-led discovery.",
|
|
5
|
-
"skillVersion": "2.0.
|
|
6
|
-
"trackopsVersion": "2.0.
|
|
5
|
+
"skillVersion": "2.0.6",
|
|
6
|
+
"trackopsVersion": "2.0.6",
|
|
7
7
|
"npmPackage": "trackops",
|
|
8
8
|
"bootstrapPolicy": "explicit_install",
|
|
9
9
|
"supportedAgentsV1": [
|