@xera-ai/cli 0.12.2 → 0.13.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/dist/index.js +103 -2
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -15,7 +15,104 @@ import pc from "picocolors";
|
|
|
15
15
|
import { existsSync, readFileSync } from "fs";
|
|
16
16
|
import { join } from "path";
|
|
17
17
|
import { loadConfig, readAuthState } from "@xera-ai/core";
|
|
18
|
-
|
|
18
|
+
import { parse as parseYaml } from "yaml";
|
|
19
|
+
function pushTicketChecks(checks, cwd, ticket, acFieldConfigured) {
|
|
20
|
+
const ticketDir = join(cwd, ".xera", ticket);
|
|
21
|
+
if (!existsSync(ticketDir)) {
|
|
22
|
+
checks.push({
|
|
23
|
+
name: `${ticket}: .xera/${ticket}/ exists`,
|
|
24
|
+
ok: false,
|
|
25
|
+
message: `no artifact dir \u2014 run \`/xera-fetch ${ticket}\` first`
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const giPath = join(ticketDir, "graph-input.json");
|
|
30
|
+
if (!existsSync(giPath)) {
|
|
31
|
+
checks.push({
|
|
32
|
+
name: `${ticket}: graph-input.json present`,
|
|
33
|
+
ok: false,
|
|
34
|
+
message: `missing \u2014 modifiesAreas will be []; run step 5 of /xera-fetch (extract-areas prompt)`
|
|
35
|
+
});
|
|
36
|
+
} else {
|
|
37
|
+
try {
|
|
38
|
+
const data = JSON.parse(readFileSync(giPath, "utf8"));
|
|
39
|
+
if (!Array.isArray(data.modifiesAreas)) {
|
|
40
|
+
checks.push({
|
|
41
|
+
name: `${ticket}: graph-input.json present`,
|
|
42
|
+
ok: false,
|
|
43
|
+
message: `parsed but modifiesAreas is not an array \u2014 re-run step 5 of /xera-fetch`
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
checks.push({
|
|
47
|
+
name: `${ticket}: graph-input.json present`,
|
|
48
|
+
ok: true,
|
|
49
|
+
message: `${data.modifiesAreas.length} area(s)`
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
checks.push({
|
|
54
|
+
name: `${ticket}: graph-input.json present`,
|
|
55
|
+
ok: false,
|
|
56
|
+
message: `invalid JSON (${e.message}) \u2014 re-run step 5 of /xera-fetch`
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const storyPath = join(ticketDir, "story.md");
|
|
61
|
+
if (!existsSync(storyPath)) {
|
|
62
|
+
checks.push({
|
|
63
|
+
name: `${ticket}: story.md acceptanceCriteria`,
|
|
64
|
+
ok: false,
|
|
65
|
+
message: `story.md missing \u2014 re-run /xera-fetch ${ticket}`
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const raw = readFileSync(storyPath, "utf8");
|
|
70
|
+
const m = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
71
|
+
if (!m) {
|
|
72
|
+
checks.push({
|
|
73
|
+
name: `${ticket}: story.md acceptanceCriteria`,
|
|
74
|
+
ok: false,
|
|
75
|
+
message: `frontmatter missing \u2014 re-run /xera-fetch ${ticket}`
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
let fm;
|
|
80
|
+
try {
|
|
81
|
+
fm = parseYaml(m[1]);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
checks.push({
|
|
84
|
+
name: `${ticket}: story.md acceptanceCriteria`,
|
|
85
|
+
ok: false,
|
|
86
|
+
message: `frontmatter unparseable (${e.message})`
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const ac = Array.isArray(fm.acceptanceCriteria) ? fm.acceptanceCriteria : [];
|
|
91
|
+
const source = fm.acceptanceCriteriaSource === "jira-field" || fm.acceptanceCriteriaSource === "body-extraction" || fm.acceptanceCriteriaSource === "none" ? fm.acceptanceCriteriaSource : undefined;
|
|
92
|
+
if (ac.length > 0) {
|
|
93
|
+
const suffix = source ? ` from ${source}` : "";
|
|
94
|
+
checks.push({
|
|
95
|
+
name: `${ticket}: story.md acceptanceCriteria`,
|
|
96
|
+
ok: true,
|
|
97
|
+
message: `${ac.length} AC item(s)${suffix}`
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
let hint;
|
|
102
|
+
if (source === "none") {
|
|
103
|
+
hint = acFieldConfigured ? `jira.fields.acceptanceCriteria is configured but Jira returned no AC for this ticket, and /xera-fetch step 4 found no AC section in the body \u2014 add AC to the Jira ticket` : `AC not in Jira (no custom field configured) and /xera-fetch step 4 found no AC section in the description body \u2014 add AC/DoD to the Jira ticket, or edit story.md frontmatter manually`;
|
|
104
|
+
} else if (source === "body-extraction") {
|
|
105
|
+
hint = `acceptanceCriteriaSource: body-extraction but acceptanceCriteria is empty \u2014 re-run /xera-fetch ${ticket}`;
|
|
106
|
+
} else {
|
|
107
|
+
hint = acFieldConfigured ? `jira.fields.acceptanceCriteria is configured but Jira returned no AC for this ticket \u2014 check the ticket in Jira` : `no AC in frontmatter; AC-level coverage will be empty. Re-run /xera-fetch ${ticket} so step 4 can extract AC from the body (set jira.fields.acceptanceCriteria in xera.config.ts if your project uses a dedicated Jira field)`;
|
|
108
|
+
}
|
|
109
|
+
checks.push({
|
|
110
|
+
name: `${ticket}: story.md acceptanceCriteria`,
|
|
111
|
+
ok: false,
|
|
112
|
+
message: hint
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async function runChecks(cwd, opts = {}) {
|
|
19
116
|
const checks = [];
|
|
20
117
|
checks.push({
|
|
21
118
|
name: `bun ${process.versions.bun ?? "unknown"}`,
|
|
@@ -181,6 +278,10 @@ async function runChecks(cwd) {
|
|
|
181
278
|
}
|
|
182
279
|
} catch {}
|
|
183
280
|
}
|
|
281
|
+
if (opts.ticket) {
|
|
282
|
+
const acFieldConfigured = Boolean(cfg.jira?.fields?.acceptanceCriteria);
|
|
283
|
+
pushTicketChecks(checks, cwd, opts.ticket, acFieldConfigured);
|
|
284
|
+
}
|
|
184
285
|
} catch (e) {
|
|
185
286
|
checks.push({
|
|
186
287
|
name: "xera.config.ts found and valid",
|
|
@@ -261,7 +362,7 @@ async function doctorCommand(opts) {
|
|
|
261
362
|
console.log("Token usage estimation requires log lines with tokens_in/tokens_out fields (added by skills).");
|
|
262
363
|
return 0;
|
|
263
364
|
}
|
|
264
|
-
const checks = await runChecks(cwd);
|
|
365
|
+
const checks = await runChecks(cwd, opts.strict ? { ticket: opts.strict } : {});
|
|
265
366
|
for (const c of checks) {
|
|
266
367
|
const icon = c.ok ? pc.green("\u2713") : pc.red("\u2717");
|
|
267
368
|
console.log(`${icon} ${c.name}${c.message ? pc.dim(` \u2014 ${c.message}`) : ""}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xera-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"xera": "./bin/xera"
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
"typecheck": "tsc --noEmit"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@xera-ai/core": "^0.
|
|
19
|
-
"@xera-ai/skills": "^0.
|
|
18
|
+
"@xera-ai/core": "^0.13.0",
|
|
19
|
+
"@xera-ai/skills": "^0.13.0",
|
|
20
20
|
"@clack/prompts": "1.4.0",
|
|
21
21
|
"cac": "7.0.0",
|
|
22
|
-
"picocolors": "1.1.1"
|
|
22
|
+
"picocolors": "1.1.1",
|
|
23
|
+
"yaml": "2.9.0"
|
|
23
24
|
}
|
|
24
25
|
}
|