@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.
Files changed (2) hide show
  1. package/dist/index.js +103 -2
  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
- async function runChecks(cwd) {
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.12.2",
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.12.2",
19
- "@xera-ai/skills": "^0.12.2",
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
  }