@xera-ai/core 0.11.6 → 0.12.1

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.
@@ -8559,6 +8559,7 @@ var init_graph_backfill = __esm(() => {
8559
8559
 
8560
8560
  // bin/internal.ts
8561
8561
  var import_dotenv = __toESM(require_main(), 1);
8562
+ import { existsSync as existsSync33 } from "fs";
8562
8563
 
8563
8564
  // src/bin-internal/ac-coverage-backfill-finalize.ts
8564
8565
  init_store();
@@ -8797,7 +8798,7 @@ var CoverageSchema = z3.object({
8797
8798
  criticalAreas: z3.array(z3.string().regex(/^[a-z0-9-]+$/)).default([]),
8798
8799
  autoSnapshotOnCoverage: z3.boolean().default(true)
8799
8800
  }).prefault({});
8800
- var XeraConfigSchema = z3.object({
8801
+ var XeraConfigSchema = z3.strictObject({
8801
8802
  jira: JiraSchema,
8802
8803
  web: WebSchema.optional(),
8803
8804
  http: HttpSchema.optional(),
@@ -8852,6 +8853,46 @@ async function authSetupCmd(argv) {
8852
8853
  }
8853
8854
  const mod = await import(pathToFileURL2(authSetupScript).href);
8854
8855
  let exitCode = 0;
8856
+ const shapeRequestsWeb = opts.shape === "all" || opts.shape === "web";
8857
+ const shapeRequestsHttp = opts.shape === "all" || opts.shape === "http";
8858
+ const explicit = opts.shape !== "all";
8859
+ if (shapeRequestsWeb && config.web && typeof mod.web !== "function") {
8860
+ console.error(`[xera:auth-setup] web adapter is configured in xera.config.ts but shared/auth-setup.ts is missing the \`web\` export.
8861
+ ` + ` Add: \`export const web = defineAuthSetup(async (page, role, creds) => { ... })\` \u2014 see docs/CONFIGURATION.md`);
8862
+ exitCode = 1;
8863
+ }
8864
+ if (shapeRequestsHttp && config.http && typeof mod.http !== "function") {
8865
+ console.error(`[xera:auth-setup] http adapter is configured in xera.config.ts but shared/auth-setup.ts is missing the \`http\` export.
8866
+ ` + ` Add: \`export const http = defineHttpAuthSetup(async (request, role, creds) => { ... })\` \u2014 see docs/CONFIGURATION.md`);
8867
+ exitCode = 1;
8868
+ }
8869
+ if (explicit && opts.shape === "web" && !config.web) {
8870
+ console.error(`[xera:auth-setup] --shape web requested, but xera.config.ts has no \`web\` block. Add a web: {...} block or use --shape http/all.`);
8871
+ exitCode = 1;
8872
+ }
8873
+ if (explicit && opts.shape === "http" && !config.http) {
8874
+ console.error(`[xera:auth-setup] --shape http requested, but xera.config.ts has no \`http\` block. Add an http: {...} block or use --shape web/all.`);
8875
+ exitCode = 1;
8876
+ }
8877
+ if (!config.web && !config.http) {
8878
+ console.error(`[xera:auth-setup] no \`web\` or \`http\` block found in xera.config.ts \u2014 nothing to authenticate.`);
8879
+ exitCode = 1;
8880
+ }
8881
+ if (opts.role !== undefined) {
8882
+ const webRoles = shapeRequestsWeb && config.web ? Object.keys(config.web.auth.roles) : [];
8883
+ const httpRoles = shapeRequestsHttp && config.http ? Object.keys(config.http.auth.roles) : [];
8884
+ const allRoles = Array.from(new Set([...webRoles, ...httpRoles]));
8885
+ if (allRoles.length > 0 && !allRoles.includes(opts.role)) {
8886
+ const detail = [];
8887
+ if (webRoles.length > 0)
8888
+ detail.push(`web roles: ${webRoles.join(", ")}`);
8889
+ if (httpRoles.length > 0)
8890
+ detail.push(`http roles: ${httpRoles.join(", ")}`);
8891
+ console.error(`[xera:auth-setup] unknown role '${opts.role}' \u2014 configured roles: ${allRoles.join(", ")}
8892
+ (${detail.join("; ")})`);
8893
+ return 1;
8894
+ }
8895
+ }
8855
8896
  if ((opts.shape === "all" || opts.shape === "web") && config.web && typeof mod.web === "function") {
8856
8897
  const webConfig = config.web;
8857
8898
  const envName = process.env.XERA_ENV ?? webConfig.defaultEnv;
@@ -10544,21 +10585,270 @@ async function execCmd(argv) {
10544
10585
  }
10545
10586
  }
10546
10587
 
10588
+ // src/bin-internal/explore-finalize.ts
10589
+ import { appendFileSync as appendFileSync3, existsSync as existsSync19, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
10590
+ import { join as join18 } from "path";
10591
+ import { z as z7 } from "zod";
10592
+ var CATEGORY_ENUM = z7.enum([
10593
+ "negative",
10594
+ "boundary",
10595
+ "state-combination",
10596
+ "race",
10597
+ "error-recovery",
10598
+ "a11y",
10599
+ "security-smell",
10600
+ "non-functional"
10601
+ ]);
10602
+ var ProposalsSchema = z7.object({
10603
+ proposals: z7.array(z7.object({
10604
+ id: z7.string().min(1),
10605
+ ticketId: z7.string().min(1),
10606
+ category: CATEGORY_ENUM,
10607
+ severity: z7.enum(["low", "medium", "high"]),
10608
+ title: z7.string().min(1),
10609
+ rationale: z7.string().min(1),
10610
+ gherkin: z7.string().min(1)
10611
+ }))
10612
+ });
10613
+ function parseArgs4(argv) {
10614
+ let ticket;
10615
+ let accept;
10616
+ for (let i = 0;i < argv.length; i++) {
10617
+ const a = argv[i];
10618
+ if (a === "--accept") {
10619
+ const v = argv[++i];
10620
+ if (v !== undefined)
10621
+ accept = v;
10622
+ } else if (a === "--help-stub") {} else if (a && !a.startsWith("--") && ticket === undefined) {
10623
+ ticket = a;
10624
+ } else {
10625
+ return { error: `unknown flag: ${a}` };
10626
+ }
10627
+ }
10628
+ if (!ticket)
10629
+ return { error: "ticket key is required as a positional argument" };
10630
+ if (!accept)
10631
+ return { error: '--accept is required (comma-separated IDs, "all", or "high-only")' };
10632
+ return { ticket, accept };
10633
+ }
10634
+ function selectProposals(all, accept) {
10635
+ const trimmed = accept.trim();
10636
+ if (trimmed === "all")
10637
+ return all;
10638
+ if (trimmed === "high-only")
10639
+ return all.filter((p) => p.severity === "high");
10640
+ const ids = new Set(trimmed.split(",").map((s) => s.trim()).filter(Boolean));
10641
+ if (ids.size === 0)
10642
+ return { error: "no IDs supplied" };
10643
+ const picked = all.filter((p) => ids.has(p.id));
10644
+ const found = new Set(picked.map((p) => p.id));
10645
+ const missing = [...ids].filter((id) => !found.has(id));
10646
+ if (missing.length > 0)
10647
+ return { error: `unknown proposal IDs: ${missing.join(", ")}` };
10648
+ return picked;
10649
+ }
10650
+ function ensureFeatureHeader(ticketDir, ticket) {
10651
+ const explorePath = join18(ticketDir, "explore.feature");
10652
+ if (existsSync19(explorePath))
10653
+ return explorePath;
10654
+ const testFeaturePath = join18(ticketDir, "test.feature");
10655
+ let header;
10656
+ if (existsSync19(testFeaturePath)) {
10657
+ const testContent = readFileSync16(testFeaturePath, "utf8");
10658
+ const firstFeatureMatch = testContent.match(/^Feature:.*$/m);
10659
+ header = firstFeatureMatch ? `${firstFeatureMatch[0]} (adversarial)
10660
+ Adversarial scenarios beyond the acceptance criteria.
10661
+ Generated by /xera-explore \u2014 review before merging into test.feature.
10662
+
10663
+ ` : `Feature: ${ticket} adversarial
10664
+
10665
+ `;
10666
+ } else {
10667
+ header = `Feature: ${ticket} adversarial
10668
+ Adversarial scenarios beyond the acceptance criteria.
10669
+ Generated by /xera-explore \u2014 review before merging into test.feature.
10670
+
10671
+ `;
10672
+ }
10673
+ writeFileSync10(explorePath, header);
10674
+ return explorePath;
10675
+ }
10676
+ function formatScenario(p) {
10677
+ const tags = `@adversarial @adversarial-${p.category} @severity-${p.severity}`;
10678
+ const rationaleComment = ` # ${p.rationale}`;
10679
+ const indented = p.gherkin.split(`
10680
+ `).map((line) => line.startsWith("Scenario:") ? ` ${line}` : line ? ` ${line}` : line).join(`
10681
+ `);
10682
+ return `
10683
+ ${tags}
10684
+ ${rationaleComment}
10685
+ ${indented}
10686
+ `;
10687
+ }
10688
+ async function exploreFinalizeCmd(argv) {
10689
+ const parsed = parseArgs4(argv);
10690
+ if ("error" in parsed) {
10691
+ console.error(`[explore-finalize] ${parsed.error}`);
10692
+ return 1;
10693
+ }
10694
+ const cwd = process.cwd();
10695
+ const ticketDir = join18(cwd, ".xera", parsed.ticket);
10696
+ const proposalsPath = join18(ticketDir, "adversarial-proposals.json");
10697
+ if (!existsSync19(proposalsPath)) {
10698
+ console.error(`[explore-finalize] adversarial-proposals.json not found for ${parsed.ticket} \u2014 run /xera-explore Step 4 first`);
10699
+ return 2;
10700
+ }
10701
+ let proposals;
10702
+ try {
10703
+ const raw = JSON.parse(readFileSync16(proposalsPath, "utf8"));
10704
+ proposals = ProposalsSchema.parse(raw);
10705
+ } catch (e) {
10706
+ console.error(`[explore-finalize] invalid proposals JSON: ${e.message}`);
10707
+ return 1;
10708
+ }
10709
+ const picked = selectProposals(proposals.proposals, parsed.accept);
10710
+ if ("error" in picked) {
10711
+ console.error(`[explore-finalize] ${picked.error}`);
10712
+ return 1;
10713
+ }
10714
+ if (picked.length === 0) {
10715
+ console.error("[explore-finalize] no proposals matched the --accept filter");
10716
+ return 1;
10717
+ }
10718
+ const explorePath = ensureFeatureHeader(ticketDir, parsed.ticket);
10719
+ for (const p of picked) {
10720
+ appendFileSync3(explorePath, formatScenario(p));
10721
+ }
10722
+ console.log(`[explore-finalize] appended ${picked.length} scenario(s) to ${explorePath} (${picked.map((p) => p.id).join(", ")})`);
10723
+ return 0;
10724
+ }
10725
+
10726
+ // src/bin-internal/explore-prepare.ts
10727
+ import { existsSync as existsSync20, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
10728
+ import { join as join19 } from "path";
10729
+ var VALID_CATEGORIES = [
10730
+ "negative",
10731
+ "boundary",
10732
+ "state-combination",
10733
+ "race",
10734
+ "error-recovery",
10735
+ "a11y",
10736
+ "security-smell",
10737
+ "non-functional"
10738
+ ];
10739
+ function parseArgs5(argv) {
10740
+ let ticket;
10741
+ let categoriesRaw = "";
10742
+ let userHint = "";
10743
+ for (let i = 0;i < argv.length; i++) {
10744
+ const a = argv[i];
10745
+ if (a === "--categories") {
10746
+ const v = argv[++i];
10747
+ if (v !== undefined)
10748
+ categoriesRaw = v;
10749
+ } else if (a === "--user-hint") {
10750
+ const v = argv[++i];
10751
+ if (v !== undefined)
10752
+ userHint = v;
10753
+ } else if (a === "--help-stub") {} else if (a && !a.startsWith("--") && ticket === undefined) {
10754
+ ticket = a;
10755
+ } else {
10756
+ return { error: `unknown flag: ${a}` };
10757
+ }
10758
+ }
10759
+ if (!ticket)
10760
+ return { error: "ticket key is required as a positional argument" };
10761
+ const categoriesInclude = [];
10762
+ for (const slug of categoriesRaw.split(",").map((s) => s.trim()).filter(Boolean)) {
10763
+ if (!VALID_CATEGORIES.includes(slug)) {
10764
+ return { error: `invalid category: ${slug}` };
10765
+ }
10766
+ categoriesInclude.push(slug);
10767
+ }
10768
+ return { ticket, categoriesInclude, userHint };
10769
+ }
10770
+ function parseStoryMd(content) {
10771
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
10772
+ if (!fmMatch)
10773
+ return { summary: "", ac: [], body: content };
10774
+ const [, fm, body] = fmMatch;
10775
+ const summaryMatch = fm.match(/^summary:\s*(.+)$/m);
10776
+ const summary = summaryMatch?.[1]?.trim() ?? "";
10777
+ const ac = [];
10778
+ const acBlock = fm.match(/^ac:\s*\n((?:\s*-\s.+\n?)+)/m);
10779
+ if (acBlock) {
10780
+ for (const line of acBlock[1].split(`
10781
+ `)) {
10782
+ const m = line.match(/^\s*-\s*(.+)$/);
10783
+ if (m)
10784
+ ac.push(m[1].trim());
10785
+ }
10786
+ }
10787
+ return { summary, ac, body: body.trim() };
10788
+ }
10789
+ async function explorePrepareCmd(argv) {
10790
+ const parsed = parseArgs5(argv);
10791
+ if ("error" in parsed) {
10792
+ console.error(`[explore-prepare] ${parsed.error}`);
10793
+ return 1;
10794
+ }
10795
+ const cwd = process.cwd();
10796
+ const configPath = join19(cwd, "xera.config.ts");
10797
+ if (!existsSync20(configPath)) {
10798
+ console.error("[explore-prepare] xera.config.ts not found \u2014 run inside a xera project");
10799
+ return 2;
10800
+ }
10801
+ const ticketDir = join19(cwd, ".xera", parsed.ticket);
10802
+ const storyPath = join19(ticketDir, "story.md");
10803
+ if (!existsSync20(storyPath)) {
10804
+ console.error(`[explore-prepare] no story for ${parsed.ticket} \u2014 run /xera-fetch ${parsed.ticket} first`);
10805
+ return 2;
10806
+ }
10807
+ const story = readFileSync17(storyPath, "utf8");
10808
+ const { summary, ac, body } = parseStoryMd(story);
10809
+ let adapter = "web";
10810
+ const metaPath = join19(ticketDir, "meta.json");
10811
+ if (existsSync20(metaPath)) {
10812
+ try {
10813
+ const meta = JSON.parse(readFileSync17(metaPath, "utf8"));
10814
+ if (meta.adapter === "http")
10815
+ adapter = "http";
10816
+ } catch {}
10817
+ }
10818
+ const input = {
10819
+ ticket: { id: parsed.ticket, summary, story: body, ac },
10820
+ adapter,
10821
+ categoriesInclude: parsed.categoriesInclude
10822
+ };
10823
+ const featurePath = join19(ticketDir, "test.feature");
10824
+ if (existsSync20(featurePath))
10825
+ input.existingFeature = readFileSync17(featurePath, "utf8");
10826
+ const specPath = join19(ticketDir, "spec.ts");
10827
+ if (existsSync20(specPath))
10828
+ input.existingSpec = readFileSync17(specPath, "utf8");
10829
+ if (parsed.userHint)
10830
+ input.userHint = parsed.userHint;
10831
+ const outPath = join19(ticketDir, "adversarial-input.json");
10832
+ writeFileSync11(outPath, JSON.stringify(input, null, 2));
10833
+ console.log(`[explore-prepare] wrote ${outPath}`);
10834
+ return 0;
10835
+ }
10836
+
10547
10837
  // src/bin-internal/fetch.ts
10548
- import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "fs";
10838
+ import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync13 } from "fs";
10549
10839
  import { dirname as dirname5 } from "path";
10550
10840
 
10551
10841
  // src/artifact/hash.ts
10552
10842
  import { createHash as createHash4 } from "crypto";
10553
- import { existsSync as existsSync19, readFileSync as readFileSync16 } from "fs";
10843
+ import { existsSync as existsSync21, readFileSync as readFileSync18 } from "fs";
10554
10844
  function hashString(s) {
10555
10845
  return `sha256:${createHash4("sha256").update(s).digest("hex")}`;
10556
10846
  }
10557
10847
  function hashFile(path) {
10558
- return hashString(readFileSync16(path, "utf8"));
10848
+ return hashString(readFileSync18(path, "utf8"));
10559
10849
  }
10560
10850
  function hashFileIfExists(path) {
10561
- if (!existsSync19(path))
10851
+ if (!existsSync21(path))
10562
10852
  return null;
10563
10853
  return hashFile(path);
10564
10854
  }
@@ -10567,33 +10857,33 @@ function hashFileIfExists(path) {
10567
10857
  init_paths2();
10568
10858
 
10569
10859
  // src/jira/mcp-backend.ts
10570
- import { existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
10860
+ import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync19, writeFileSync as writeFileSync12 } from "fs";
10571
10861
  import { tmpdir } from "os";
10572
- import { join as join18 } from "path";
10862
+ import { join as join20 } from "path";
10573
10863
  var MCP_ENV = "XERA_MCP_JIRA";
10574
10864
  async function createMcpBackend(_baseUrl) {
10575
10865
  if (process.env[MCP_ENV] !== "1")
10576
10866
  return null;
10577
- const tmpDir = join18(tmpdir(), "xera-mcp");
10867
+ const tmpDir = join20(tmpdir(), "xera-mcp");
10578
10868
  mkdirSync11(tmpDir, { recursive: true });
10579
10869
  return {
10580
10870
  backend: "mcp",
10581
10871
  async fetchTicket(key, _fields) {
10582
- const cachePath = join18(tmpDir, `${key}.json`);
10583
- if (!existsSync20(cachePath)) {
10872
+ const cachePath = join20(tmpDir, `${key}.json`);
10873
+ if (!existsSync22(cachePath)) {
10584
10874
  throw new Error(`MCP-mode fetch requires the skill to first call mcp__atlassian__getJiraIssue and write ${cachePath}. ` + `If you are running this directly, unset ${MCP_ENV} to use REST.`);
10585
10875
  }
10586
- const parsed = JSON.parse(readFileSync17(cachePath, "utf8"));
10876
+ const parsed = JSON.parse(readFileSync19(cachePath, "utf8"));
10587
10877
  return parsed;
10588
10878
  },
10589
10879
  async postComment(key, body) {
10590
- const outPath = join18(tmpDir, `${key}.comment.json`);
10591
- writeFileSync10(outPath, JSON.stringify({ key, body }));
10880
+ const outPath = join20(tmpDir, `${key}.comment.json`);
10881
+ writeFileSync12(outPath, JSON.stringify({ key, body }));
10592
10882
  return { id: "mcp-pending" };
10593
10883
  },
10594
10884
  async transitionStatus(key, statusName) {
10595
- const outPath = join18(tmpDir, `${key}.transition.json`);
10596
- writeFileSync10(outPath, JSON.stringify({ key, statusName }));
10885
+ const outPath = join20(tmpDir, `${key}.transition.json`);
10886
+ writeFileSync12(outPath, JSON.stringify({ key, statusName }));
10597
10887
  },
10598
10888
  async listFields(_sampleKey) {
10599
10889
  throw new Error("listFields is REST-only; init flow uses REST for field discovery.");
@@ -10747,7 +11037,7 @@ async function fetchCmd(argv, opts = {}) {
10747
11037
  const acLines = parseAcLines(t.acceptanceCriteria);
10748
11038
  const full = renderStory(t.key, t.summary, storyHash, acLines, body);
10749
11039
  mkdirSync12(dirname5(paths.storyPath), { recursive: true });
10750
- writeFileSync11(paths.storyPath, full);
11040
+ writeFileSync13(paths.storyPath, full);
10751
11041
  const existing = readMeta(paths.metaPath);
10752
11042
  writeMeta(paths.metaPath, {
10753
11043
  ticket,
@@ -10808,20 +11098,20 @@ function renderStory(key, summary, storyHash, acLines, body) {
10808
11098
  }
10809
11099
 
10810
11100
  // src/bin-internal/fill-gap-finalize.ts
10811
- import { existsSync as existsSync21, mkdirSync as mkdirSync13, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "fs";
10812
- import { join as join19 } from "path";
10813
- import { z as z7 } from "zod";
10814
- var ProposalsSchema = z7.object({
10815
- proposals: z7.array(z7.object({
10816
- id: z7.string().min(1),
10817
- ticketId: z7.string().min(1),
10818
- title: z7.string().min(1),
10819
- rationale: z7.string().min(1),
10820
- gherkin: z7.string().min(1),
10821
- satisfiesAcs: z7.array(z7.number().int().nonnegative())
11101
+ import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync20, writeFileSync as writeFileSync14 } from "fs";
11102
+ import { join as join21 } from "path";
11103
+ import { z as z8 } from "zod";
11104
+ var ProposalsSchema2 = z8.object({
11105
+ proposals: z8.array(z8.object({
11106
+ id: z8.string().min(1),
11107
+ ticketId: z8.string().min(1),
11108
+ title: z8.string().min(1),
11109
+ rationale: z8.string().min(1),
11110
+ gherkin: z8.string().min(1),
11111
+ satisfiesAcs: z8.array(z8.number().int().nonnegative())
10822
11112
  }))
10823
11113
  });
10824
- function parseArgs4(argv) {
11114
+ function parseArgs6(argv) {
10825
11115
  let accept;
10826
11116
  let ticket;
10827
11117
  let source;
@@ -10871,21 +11161,21 @@ function formatDraft(ticketId, proposal) {
10871
11161
  }
10872
11162
  async function fillGapFinalizeCmd(argv) {
10873
11163
  if (argv.includes("--help-stub")) {}
10874
- const parsed = parseArgs4(argv);
11164
+ const parsed = parseArgs6(argv);
10875
11165
  if ("error" in parsed) {
10876
11166
  console.error(`[fill-gap-finalize] ${parsed.error}`);
10877
11167
  return 1;
10878
11168
  }
10879
11169
  const cwd = process.cwd();
10880
- const sourcePath = parsed.source ?? join19(cwd, ".xera/coverage/proposals.json");
10881
- if (!existsSync21(sourcePath)) {
11170
+ const sourcePath = parsed.source ?? join21(cwd, ".xera/coverage/proposals.json");
11171
+ if (!existsSync23(sourcePath)) {
10882
11172
  console.error(`[fill-gap-finalize] source not found: ${sourcePath}`);
10883
11173
  return 2;
10884
11174
  }
10885
11175
  let proposals;
10886
11176
  try {
10887
- const raw = JSON.parse(readFileSync18(sourcePath, "utf8"));
10888
- proposals = ProposalsSchema.parse(raw);
11177
+ const raw = JSON.parse(readFileSync20(sourcePath, "utf8"));
11178
+ proposals = ProposalsSchema2.parse(raw);
10889
11179
  } catch (e) {
10890
11180
  console.error(`[fill-gap-finalize] invalid proposals: ${e.message}`);
10891
11181
  return 2;
@@ -10895,22 +11185,22 @@ async function fillGapFinalizeCmd(argv) {
10895
11185
  console.error(`[fill-gap-finalize] proposal id "${parsed.accept}" not in source`);
10896
11186
  return 2;
10897
11187
  }
10898
- const ticketDir = join19(cwd, ".xera", parsed.ticket);
11188
+ const ticketDir = join21(cwd, ".xera", parsed.ticket);
10899
11189
  mkdirSync13(ticketDir, { recursive: true });
10900
- const draftPath = join19(ticketDir, "feature.draft.md");
10901
- if (existsSync21(draftPath) && !parsed.force) {
11190
+ const draftPath = join21(ticketDir, "feature.draft.md");
11191
+ if (existsSync23(draftPath) && !parsed.force) {
10902
11192
  console.error(`[fill-gap-finalize] ${draftPath} exists; pass --force to overwrite`);
10903
11193
  return 3;
10904
11194
  }
10905
- writeFileSync12(draftPath, formatDraft(parsed.ticket, proposal));
11195
+ writeFileSync14(draftPath, formatDraft(parsed.ticket, proposal));
10906
11196
  return 0;
10907
11197
  }
10908
11198
 
10909
11199
  // src/bin-internal/fill-gap-prepare.ts
10910
11200
  init_store();
10911
- import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync13 } from "fs";
10912
- import { join as join20 } from "path";
10913
- function parseArgs5(argv) {
11201
+ import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync15 } from "fs";
11202
+ import { join as join22 } from "path";
11203
+ function parseArgs7(argv) {
10914
11204
  const args = {};
10915
11205
  for (let i = 0;i < argv.length; i++) {
10916
11206
  const a = argv[i];
@@ -10976,7 +11266,7 @@ function buildTicketContext(snap, ticketId) {
10976
11266
  };
10977
11267
  }
10978
11268
  async function fillGapPrepareCmd(argv) {
10979
- const parsed = parseArgs5(argv);
11269
+ const parsed = parseArgs7(argv);
10980
11270
  if ("error" in parsed) {
10981
11271
  console.error(`[fill-gap-prepare] ${parsed.error}`);
10982
11272
  return 1;
@@ -11000,9 +11290,9 @@ async function fillGapPrepareCmd(argv) {
11000
11290
  return 2;
11001
11291
  }
11002
11292
  }
11003
- const outDir = parsed.outputDir ?? join20(cwd, ".xera/coverage", scope);
11293
+ const outDir = parsed.outputDir ?? join22(cwd, ".xera/coverage", scope);
11004
11294
  mkdirSync14(outDir, { recursive: true });
11005
- writeFileSync13(join20(outDir, "context.json"), JSON.stringify(context, null, 2));
11295
+ writeFileSync15(join22(outDir, "context.json"), JSON.stringify(context, null, 2));
11006
11296
  return 0;
11007
11297
  }
11008
11298
 
@@ -11012,18 +11302,18 @@ init_graph_backfill();
11012
11302
  // src/graph/enrich.ts
11013
11303
  init_store();
11014
11304
  init_ulid();
11015
- import { existsSync as existsSync22, readFileSync as readFileSync19, unlinkSync as unlinkSync2 } from "fs";
11016
- import { join as join21 } from "path";
11017
- import { z as z8 } from "zod";
11305
+ import { existsSync as existsSync24, readFileSync as readFileSync21, unlinkSync as unlinkSync2 } from "fs";
11306
+ import { join as join23 } from "path";
11307
+ import { z as z9 } from "zod";
11018
11308
  var MAX_SIMILAR_EDGES = 10;
11019
11309
  var MIN_CONFIDENCE = 0.7;
11020
- var SimilarEntrySchema = z8.object({
11021
- ticketId: z8.string().regex(/^[A-Z][A-Z0-9]*-\d+$/),
11022
- confidence: z8.number(),
11023
- reason: z8.string()
11310
+ var SimilarEntrySchema = z9.object({
11311
+ ticketId: z9.string().regex(/^[A-Z][A-Z0-9]*-\d+$/),
11312
+ confidence: z9.number(),
11313
+ reason: z9.string()
11024
11314
  });
11025
- var EnrichmentInputSchema = z8.object({
11026
- similar: z8.array(SimilarEntrySchema)
11315
+ var EnrichmentInputSchema = z9.object({
11316
+ similar: z9.array(SimilarEntrySchema)
11027
11317
  });
11028
11318
  var nowIso3 = () => new Date().toISOString();
11029
11319
  var mk2 = (actor, type, payload) => ({
@@ -11042,11 +11332,11 @@ async function enrichTicket(repoRoot, ticketId, opts) {
11042
11332
  if (snapshot.tickets[ticketId].enrichedAt && !opts.force) {
11043
11333
  return { ticketId, similarCount: 0, enrichedAt: snapshot.tickets[ticketId].enrichedAt };
11044
11334
  }
11045
- const inputPath = join21(repoRoot, ".xera", ticketId, "enrichment-input.json");
11046
- if (!existsSync22(inputPath)) {
11335
+ const inputPath = join23(repoRoot, ".xera", ticketId, "enrichment-input.json");
11336
+ if (!existsSync24(inputPath)) {
11047
11337
  throw new Error(`enrichment-input.json not found at ${inputPath}`);
11048
11338
  }
11049
- const raw = JSON.parse(readFileSync19(inputPath, "utf8"));
11339
+ const raw = JSON.parse(readFileSync21(inputPath, "utf8"));
11050
11340
  const parsed = EnrichmentInputSchema.safeParse(raw);
11051
11341
  if (!parsed.success) {
11052
11342
  throw new Error(`invalid enrichment-input.json: ${parsed.error.message}`);
@@ -11151,12 +11441,12 @@ async function graphQueryCmd(argv) {
11151
11441
  init_graph_record();
11152
11442
 
11153
11443
  // src/bin-internal/graph-render.ts
11154
- import { existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync21, renameSync as renameSync2, writeFileSync as writeFileSync14 } from "fs";
11155
- import { dirname as dirname7, join as join23 } from "path";
11444
+ import { existsSync as existsSync25, mkdirSync as mkdirSync15, readFileSync as readFileSync23, renameSync as renameSync2, writeFileSync as writeFileSync16 } from "fs";
11445
+ import { dirname as dirname7, join as join25 } from "path";
11156
11446
 
11157
11447
  // src/graph/render.ts
11158
- import { readFileSync as readFileSync20 } from "fs";
11159
- import { dirname as dirname6, join as join22 } from "path";
11448
+ import { readFileSync as readFileSync22 } from "fs";
11449
+ import { dirname as dirname6, join as join24 } from "path";
11160
11450
  import { fileURLToPath } from "url";
11161
11451
  var COLORS = {
11162
11452
  ticket: "#3B82F6",
@@ -11375,9 +11665,9 @@ function transformForVisNetwork(snap, opts) {
11375
11665
  }
11376
11666
  var __filename2 = fileURLToPath(import.meta.url);
11377
11667
  var __dirname2 = dirname6(__filename2);
11378
- var TEMPLATES_DIR = join22(__dirname2, "templates");
11668
+ var TEMPLATES_DIR = join24(__dirname2, "templates");
11379
11669
  function loadTemplate(name) {
11380
- return readFileSync20(join22(TEMPLATES_DIR, name), "utf8");
11670
+ return readFileSync22(join24(TEMPLATES_DIR, name), "utf8");
11381
11671
  }
11382
11672
  function statsToHuman(s) {
11383
11673
  return `${s.tickets} tickets \xB7 ${s.scenarios} scenarios \xB7 ${s.poms} POMs \xB7 ${s.edges} edges`;
@@ -11429,7 +11719,7 @@ async function graphRenderCmd(argv) {
11429
11719
  includeCoverage = true;
11430
11720
  }
11431
11721
  const repoRoot = process.cwd();
11432
- const finalPath = outPath ?? join23(repoRoot, ".xera/graph.html");
11722
+ const finalPath = outPath ?? join25(repoRoot, ".xera/graph.html");
11433
11723
  const events = loadAllEvents(repoRoot);
11434
11724
  const snap = deriveSnapshot(events);
11435
11725
  const totalNodeCount = Object.keys(snap.tickets).length + Object.keys(snap.scenarios).length + Object.keys(snap.poms).length + Object.keys(snap.areas).length;
@@ -11437,7 +11727,7 @@ async function graphRenderCmd(argv) {
11437
11727
  if (performanceMode === "text-fallback") {
11438
11728
  const txtPath = finalPath.replace(/\.html$/, ".txt");
11439
11729
  mkdirSync15(dirname7(txtPath), { recursive: true });
11440
- writeFileSync14(txtPath, `Graph too large for HTML viewer (${totalNodeCount} nodes). Use 'xera:graph-query --format text' instead.
11730
+ writeFileSync16(txtPath, `Graph too large for HTML viewer (${totalNodeCount} nodes). Use 'xera:graph-query --format text' instead.
11441
11731
  `);
11442
11732
  console.log(`[graph-render] graph too large (${totalNodeCount} nodes); wrote ${txtPath}`);
11443
11733
  return 0;
@@ -11449,9 +11739,9 @@ async function graphRenderCmd(argv) {
11449
11739
  opts.since = since;
11450
11740
  let coverage;
11451
11741
  if (includeCoverage) {
11452
- const reportPath = join23(repoRoot, ".xera/coverage/report.json");
11453
- if (existsSync23(reportPath)) {
11454
- const report = JSON.parse(readFileSync21(reportPath, "utf8"));
11742
+ const reportPath = join25(repoRoot, ".xera/coverage/report.json");
11743
+ if (existsSync25(reportPath)) {
11744
+ const report = JSON.parse(readFileSync23(reportPath, "utf8"));
11455
11745
  const snapshots = events.filter((e) => e.type === "coverage.snapshot").map((e) => e.payload);
11456
11746
  coverage = { report, snapshots };
11457
11747
  } else {
@@ -11469,7 +11759,7 @@ async function graphRenderCmd(argv) {
11469
11759
  const html = renderHtml(renderInput);
11470
11760
  mkdirSync15(dirname7(finalPath), { recursive: true });
11471
11761
  const tmpPath = `${finalPath}.tmp`;
11472
- writeFileSync14(tmpPath, html);
11762
+ writeFileSync16(tmpPath, html);
11473
11763
  renameSync2(tmpPath, finalPath);
11474
11764
  console.log(`[graph-render] wrote ${finalPath} (${data.stats.tickets} tickets \xB7 ${data.stats.scenarios} scenarios \xB7 ${html.length} bytes)`);
11475
11765
  return 0;
@@ -11500,8 +11790,8 @@ async function graphSnapshotCmd(argv) {
11500
11790
  }
11501
11791
 
11502
11792
  // src/bin-internal/heal-prepare.ts
11503
- import { existsSync as existsSync24, readdirSync as readdirSync6, readFileSync as readFileSync22, writeFileSync as writeFileSync15 } from "fs";
11504
- import { join as join24 } from "path";
11793
+ import { existsSync as existsSync26, readdirSync as readdirSync6, readFileSync as readFileSync24, writeFileSync as writeFileSync17 } from "fs";
11794
+ import { join as join26 } from "path";
11505
11795
  import { scrubFreeText } from "@xera-ai/web";
11506
11796
 
11507
11797
  // ../../node_modules/fflate/esm/index.mjs
@@ -11848,15 +12138,15 @@ function strFromU8(dat, latin1) {
11848
12138
  var slzh = function(d, b) {
11849
12139
  return b + 30 + b2(d, b + 26) + b2(d, b + 28);
11850
12140
  };
11851
- var zh = function(d, b, z9) {
12141
+ var zh = function(d, b, z10) {
11852
12142
  var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
11853
- var _a2 = z64hs(d, es, efl, z9, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
12143
+ var _a2 = z64hs(d, es, efl, z10, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2];
11854
12144
  return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
11855
12145
  };
11856
- var z64hs = function(d, b, l, z9, sc, su, off) {
12146
+ var z64hs = function(d, b, l, z10, sc, su, off) {
11857
12147
  var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
11858
12148
  var nf = nsc + nsu + noff;
11859
- if (z9 && nf) {
12149
+ if (z10 && nf) {
11860
12150
  for (;b + 4 < e; b += 4 + b2(d, b + 2)) {
11861
12151
  if (b2(d, b) == 1) {
11862
12152
  return [
@@ -11867,7 +12157,7 @@ var z64hs = function(d, b, l, z9, sc, su, off) {
11867
12157
  ];
11868
12158
  }
11869
12159
  }
11870
- if (z9 < 2)
12160
+ if (z10 < 2)
11871
12161
  err(13);
11872
12162
  }
11873
12163
  return [sc, su, off, 0];
@@ -11883,18 +12173,18 @@ function unzipSync(data, opts) {
11883
12173
  if (!c)
11884
12174
  return {};
11885
12175
  var o = b4(data, e + 16);
11886
- var z9 = b4(data, e - 20) == 117853008;
11887
- if (z9) {
12176
+ var z10 = b4(data, e - 20) == 117853008;
12177
+ if (z10) {
11888
12178
  var ze = b4(data, e - 12);
11889
- z9 = b4(data, ze) == 101075792;
11890
- if (z9) {
12179
+ z10 = b4(data, ze) == 101075792;
12180
+ if (z10) {
11891
12181
  c = b4(data, ze + 32);
11892
12182
  o = b4(data, ze + 48);
11893
12183
  }
11894
12184
  }
11895
12185
  var fltr = opts && opts.filter;
11896
12186
  for (var i2 = 0;i2 < c; ++i2) {
11897
- var _a2 = zh(data, o, z9), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
12187
+ var _a2 = zh(data, o, z10), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
11898
12188
  o = no;
11899
12189
  if (!fltr || fltr({
11900
12190
  name: fn,
@@ -11930,9 +12220,9 @@ function classifyKind(raw) {
11930
12220
  return "other";
11931
12221
  }
11932
12222
  function extractDomSnapshot(tracePath) {
11933
- if (!existsSync24(tracePath))
12223
+ if (!existsSync26(tracePath))
11934
12224
  return "";
11935
- const buf = readFileSync22(tracePath);
12225
+ const buf = readFileSync24(tracePath);
11936
12226
  const entries = unzipSync(buf);
11937
12227
  const traceKey = Object.keys(entries).find((name) => name.endsWith(".trace"));
11938
12228
  let chosenKey = null;
@@ -11980,16 +12270,16 @@ function extractDomSnapshot(tracePath) {
11980
12270
  return scrubFreeText(html);
11981
12271
  }
11982
12272
  function findPomLine(ticketDir, rawLocator) {
11983
- const pomDir = join24(ticketDir, "page-objects");
12273
+ const pomDir = join26(ticketDir, "page-objects");
11984
12274
  const candidates = [];
11985
- if (existsSync24(pomDir)) {
12275
+ if (existsSync26(pomDir)) {
11986
12276
  for (const name of readdirSync6(pomDir)) {
11987
12277
  if (name.endsWith(".ts"))
11988
- candidates.push(join24(pomDir, name));
12278
+ candidates.push(join26(pomDir, name));
11989
12279
  }
11990
12280
  }
11991
12281
  for (const file of candidates) {
11992
- const text = readFileSync22(file, "utf8");
12282
+ const text = readFileSync24(file, "utf8");
11993
12283
  const lines = text.split(`
11994
12284
  `);
11995
12285
  for (let i2 = 0;i2 < lines.length; i2++) {
@@ -12027,13 +12317,13 @@ function findGherkinStep(featureText, rawLocator) {
12027
12317
  }
12028
12318
  function healPrepare(repoRoot, ticket, runId, scenarioName) {
12029
12319
  const paths = resolveArtifactPaths(repoRoot, ticket);
12030
- const classifierPath = join24(paths.ticketDir, "classifier-input.json");
12031
- const classifier = JSON.parse(readFileSync22(classifierPath, "utf8"));
12320
+ const classifierPath = join26(paths.ticketDir, "classifier-input.json");
12321
+ const classifier = JSON.parse(readFileSync24(classifierPath, "utf8"));
12032
12322
  const cls = classifier.scenarios.find((s) => s.name === scenarioName);
12033
12323
  if (!cls)
12034
12324
  throw new Error(`scenario not found in classifier-input: "${scenarioName}"`);
12035
- const runDir = join24(paths.runsDir, runId);
12036
- const normalized = JSON.parse(readFileSync22(join24(runDir, "normalized.json"), "utf8"));
12325
+ const runDir = join26(paths.runsDir, runId);
12326
+ const normalized = JSON.parse(readFileSync24(join26(runDir, "normalized.json"), "utf8"));
12037
12327
  const normSc = normalized.scenarios.find((s) => s.name === scenarioName);
12038
12328
  if (!normSc?.failure)
12039
12329
  throw new Error(`no failure recorded for scenario "${scenarioName}"`);
@@ -12044,9 +12334,9 @@ function healPrepare(repoRoot, ticket, runId, scenarioName) {
12044
12334
  const raw = m[1].trim();
12045
12335
  const kind = classifyKind(raw);
12046
12336
  const pomLoc = findPomLine(paths.ticketDir, raw);
12047
- const featureText = readFileSync22(paths.featurePath, "utf8");
12337
+ const featureText = readFileSync24(paths.featurePath, "utf8");
12048
12338
  const gherkinStep = findGherkinStep(featureText, raw);
12049
- const domSnapshotAtFailure = extractDomSnapshot(join24(runDir, "trace.zip"));
12339
+ const domSnapshotAtFailure = extractDomSnapshot(join26(runDir, "trace.zip"));
12050
12340
  return {
12051
12341
  ticket,
12052
12342
  runId,
@@ -12066,8 +12356,8 @@ async function healPrepareCmd(argv) {
12066
12356
  try {
12067
12357
  const result = healPrepare(process.cwd(), ticket, runId, scenarioName);
12068
12358
  const paths = resolveArtifactPaths(process.cwd(), ticket);
12069
- const outPath = join24(paths.runsDir, runId, "heal-input.json");
12070
- writeFileSync15(outPath, JSON.stringify(result, null, 2));
12359
+ const outPath = join26(paths.runsDir, runId, "heal-input.json");
12360
+ writeFileSync17(outPath, JSON.stringify(result, null, 2));
12071
12361
  console.log(`[xera:heal-prepare] wrote ${outPath}`);
12072
12362
  return 0;
12073
12363
  } catch (err2) {
@@ -12077,8 +12367,8 @@ async function healPrepareCmd(argv) {
12077
12367
  }
12078
12368
 
12079
12369
  // src/bin-internal/impact-prepare.ts
12080
- import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync16 } from "fs";
12081
- import { join as join25 } from "path";
12370
+ import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync18 } from "fs";
12371
+ import { join as join27 } from "path";
12082
12372
 
12083
12373
  // src/graph/impact.ts
12084
12374
  var PRIORITY_WEIGHT = { p0: 3, p1: 2, p2: 1 };
@@ -12328,11 +12618,11 @@ async function impactPrepareCmd(argv) {
12328
12618
  scenarios,
12329
12619
  generatedAt: new Date().toISOString()
12330
12620
  };
12331
- const impactDir = join25(repoRoot, ".xera/impact");
12621
+ const impactDir = join27(repoRoot, ".xera/impact");
12332
12622
  mkdirSync16(impactDir, { recursive: true });
12333
- writeFileSync16(join25(impactDir, `${ticket}.json`), JSON.stringify(report, null, 2));
12623
+ writeFileSync18(join27(impactDir, `${ticket}.json`), JSON.stringify(report, null, 2));
12334
12624
  if (!quiet) {
12335
- writeFileSync16(join25(impactDir, `${ticket}.md`), renderImpactMarkdown(report));
12625
+ writeFileSync18(join27(impactDir, `${ticket}.md`), renderImpactMarkdown(report));
12336
12626
  }
12337
12627
  return 0;
12338
12628
  }
@@ -12358,8 +12648,8 @@ async function lintCmd(argv) {
12358
12648
  }
12359
12649
 
12360
12650
  // src/bin-internal/normalize.ts
12361
- import { existsSync as existsSync25, readdirSync as readdirSync7 } from "fs";
12362
- import { join as join26 } from "path";
12651
+ import { existsSync as existsSync27, readdirSync as readdirSync7 } from "fs";
12652
+ import { join as join28 } from "path";
12363
12653
  init_paths2();
12364
12654
  async function normalizeCmd(argv) {
12365
12655
  const ticket = argv[0];
@@ -12374,8 +12664,8 @@ async function normalizeCmd(argv) {
12374
12664
  console.error("[xera:normalize] no run found");
12375
12665
  return 1;
12376
12666
  }
12377
- const runDir = join26(paths.runsDir, runId);
12378
- if (!existsSync25(runDir)) {
12667
+ const runDir = join28(paths.runsDir, runId);
12668
+ if (!existsSync27(runDir)) {
12379
12669
  console.error(`[xera:normalize] runs/${runId} missing`);
12380
12670
  return 1;
12381
12671
  }
@@ -12395,14 +12685,14 @@ async function normalizeCmd(argv) {
12395
12685
 
12396
12686
  // src/bin-internal/post.ts
12397
12687
  init_paths2();
12398
- import { existsSync as existsSync27, readFileSync as readFileSync24 } from "fs";
12399
- import { join as join27 } from "path";
12688
+ import { existsSync as existsSync29, readFileSync as readFileSync26 } from "fs";
12689
+ import { join as join29 } from "path";
12400
12690
 
12401
12691
  // src/artifact/status.ts
12402
- import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync23, writeFileSync as writeFileSync17 } from "fs";
12692
+ import { existsSync as existsSync28, mkdirSync as mkdirSync17, readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
12403
12693
  import { dirname as dirname8 } from "path";
12404
- import { z as z9 } from "zod";
12405
- var ClassificationEnum = z9.enum([
12694
+ import { z as z10 } from "zod";
12695
+ var ClassificationEnum = z10.enum([
12406
12696
  "PASS",
12407
12697
  "REAL_BUG",
12408
12698
  "SELECTOR_DRIFT",
@@ -12413,37 +12703,37 @@ var ClassificationEnum = z9.enum([
12413
12703
  "RATE_LIMITED",
12414
12704
  "AUTH_EXPIRED"
12415
12705
  ]);
12416
- var ResultEnum = z9.enum(["PASS", "FAIL"]);
12417
- var ConfidenceEnum = z9.enum(["low", "medium", "high"]);
12418
- var HistoryEntrySchema = z9.object({
12419
- ts: z9.string(),
12706
+ var ResultEnum = z10.enum(["PASS", "FAIL"]);
12707
+ var ConfidenceEnum = z10.enum(["low", "medium", "high"]);
12708
+ var HistoryEntrySchema = z10.object({
12709
+ ts: z10.string(),
12420
12710
  result: ResultEnum,
12421
12711
  class: ClassificationEnum
12422
12712
  });
12423
- var StatusJsonSchema = z9.object({
12424
- ticket: z9.string(),
12425
- lastRun: z9.string(),
12713
+ var StatusJsonSchema = z10.object({
12714
+ ticket: z10.string(),
12715
+ lastRun: z10.string(),
12426
12716
  result: ResultEnum,
12427
12717
  classification: ClassificationEnum,
12428
12718
  confidence: ConfidenceEnum,
12429
- scenarios: z9.object({
12430
- total: z9.number().int().nonnegative(),
12431
- passed: z9.number().int().nonnegative(),
12432
- failed: z9.number().int().nonnegative(),
12433
- skipped: z9.number().int().nonnegative()
12719
+ scenarios: z10.object({
12720
+ total: z10.number().int().nonnegative(),
12721
+ passed: z10.number().int().nonnegative(),
12722
+ failed: z10.number().int().nonnegative(),
12723
+ skipped: z10.number().int().nonnegative()
12434
12724
  }),
12435
- history: z9.array(HistoryEntrySchema).default([]),
12436
- last_jira_comment_id: z9.string().optional()
12725
+ history: z10.array(HistoryEntrySchema).default([]),
12726
+ last_jira_comment_id: z10.string().optional()
12437
12727
  });
12438
12728
  var HISTORY_CAP = 20;
12439
12729
  function readStatus(path) {
12440
- if (!existsSync26(path))
12730
+ if (!existsSync28(path))
12441
12731
  return null;
12442
- return StatusJsonSchema.parse(JSON.parse(readFileSync23(path, "utf8")));
12732
+ return StatusJsonSchema.parse(JSON.parse(readFileSync25(path, "utf8")));
12443
12733
  }
12444
12734
  function writeStatus(path, status) {
12445
12735
  mkdirSync17(dirname8(path), { recursive: true });
12446
- writeFileSync17(path, JSON.stringify(status, null, 2));
12736
+ writeFileSync19(path, JSON.stringify(status, null, 2));
12447
12737
  }
12448
12738
  function appendHistory(path, entry) {
12449
12739
  const s = readStatus(path);
@@ -12469,12 +12759,12 @@ async function postCmd(argv) {
12469
12759
  return 0;
12470
12760
  }
12471
12761
  const paths = resolveArtifactPaths(cwd, ticket);
12472
- const draftPath = join27(paths.ticketDir, "jira-comment.draft.md");
12473
- if (!existsSync27(draftPath)) {
12762
+ const draftPath = join29(paths.ticketDir, "jira-comment.draft.md");
12763
+ if (!existsSync29(draftPath)) {
12474
12764
  console.error(`[xera:post] no draft at ${draftPath}; run \`xera-internal report\` first.`);
12475
12765
  return 1;
12476
12766
  }
12477
- const body = readFileSync24(draftPath, "utf8");
12767
+ const body = readFileSync26(draftPath, "utf8");
12478
12768
  const client = await createJiraClient({
12479
12769
  baseUrl: config.jira.baseUrl,
12480
12770
  preferMcp: true,
@@ -12502,8 +12792,8 @@ async function promoteCmd(argv) {
12502
12792
  }
12503
12793
 
12504
12794
  // src/bin-internal/report.ts
12505
- import { existsSync as existsSync29, readFileSync as readFileSync25, writeFileSync as writeFileSync18 } from "fs";
12506
- import { join as join28 } from "path";
12795
+ import { existsSync as existsSync31, readFileSync as readFileSync27, writeFileSync as writeFileSync20 } from "fs";
12796
+ import { join as join30 } from "path";
12507
12797
  init_paths2();
12508
12798
 
12509
12799
  // src/classifier/aggregate.ts
@@ -12770,11 +13060,11 @@ xera v${input.xeraVersion} \u2022 prompts v${input.promptsVersion}`;
12770
13060
  }
12771
13061
 
12772
13062
  // src/reporter/status-writer.ts
12773
- import { existsSync as existsSync28 } from "fs";
13063
+ import { existsSync as existsSync30 } from "fs";
12774
13064
  function writeStatusFromClassification(path, input) {
12775
13065
  const result = input.classification.overall === "PASS" ? "PASS" : "FAIL";
12776
13066
  const entry = { ts: input.runTs, result, class: input.classification.overall };
12777
- if (!existsSync28(path)) {
13067
+ if (!existsSync30(path)) {
12778
13068
  writeStatus(path, {
12779
13069
  ticket: input.ticket,
12780
13070
  lastRun: input.runTs,
@@ -12808,22 +13098,22 @@ async function reportCmd(argv) {
12808
13098
  }
12809
13099
  const cwd = process.cwd();
12810
13100
  const paths = resolveArtifactPaths(cwd, ticket);
12811
- const input = JSON.parse(readFileSync25(inputArg.slice("--input=".length), "utf8"));
13101
+ const input = JSON.parse(readFileSync27(inputArg.slice("--input=".length), "utf8"));
12812
13102
  let httpRuleOverride = null;
12813
13103
  const meta = readMeta(paths.metaPath);
12814
13104
  if (meta?.adapter === "http") {
12815
13105
  const config = await loadConfig(cwd);
12816
13106
  if (config.http) {
12817
- const normalizedPath = join28(paths.ticketDir, "runs", input.runId, "normalized.json");
12818
- if (existsSync29(normalizedPath)) {
12819
- const norm = JSON.parse(readFileSync25(normalizedPath, "utf8"));
13107
+ const normalizedPath = join30(paths.ticketDir, "runs", input.runId, "normalized.json");
13108
+ if (existsSync31(normalizedPath)) {
13109
+ const norm = JSON.parse(readFileSync27(normalizedPath, "utf8"));
12820
13110
  const calls = norm.http?.calls ?? [];
12821
13111
  const rate = classifyRateLimited({ calls });
12822
13112
  if (rate)
12823
13113
  httpRuleOverride = rate;
12824
13114
  if (!httpRuleOverride) {
12825
13115
  const authFiles = {};
12826
- const httpAuthDir = join28(cwd, ".xera", ".auth", "http");
13116
+ const httpAuthDir = join30(cwd, ".xera", ".auth", "http");
12827
13117
  for (const role of Object.keys(config.http.auth.roles)) {
12828
13118
  const entry = readAuthState(httpAuthDir, role);
12829
13119
  if (entry) {
@@ -12868,8 +13158,8 @@ async function reportCmd(argv) {
12868
13158
  confidence: "high"
12869
13159
  } : s) : input.scenarios;
12870
13160
  const aggregated = aggregateScenarios(scenariosForAggregation);
12871
- const decisionsPath = join28(paths.ticketDir, "runs", input.runId, "outdated-decisions.json");
12872
- const decisions = existsSync29(decisionsPath) ? JSON.parse(readFileSync25(decisionsPath, "utf8")) : {};
13161
+ const decisionsPath = join30(paths.ticketDir, "runs", input.runId, "outdated-decisions.json");
13162
+ const decisions = existsSync31(decisionsPath) ? JSON.parse(readFileSync27(decisionsPath, "utf8")) : {};
12873
13163
  const graph = deriveSnapshot(loadAllEvents(process.cwd()));
12874
13164
  const normalizeScenarioName = (name) => name.trim().toLowerCase().replace(/\s+/g, " ");
12875
13165
  const scenarioIdByName = {};
@@ -12917,8 +13207,8 @@ async function reportCmd(argv) {
12917
13207
  xeraVersion: XERA_VERSION,
12918
13208
  promptsVersion: PROMPTS_VERSION
12919
13209
  });
12920
- const draftPath = join28(paths.ticketDir, "jira-comment.draft.md");
12921
- writeFileSync18(draftPath, md);
13210
+ const draftPath = join30(paths.ticketDir, "jira-comment.draft.md");
13211
+ writeFileSync20(draftPath, md);
12922
13212
  console.log(`[xera:report] wrote status.json and ${draftPath}`);
12923
13213
  return 0;
12924
13214
  }
@@ -12989,7 +13279,7 @@ async function unlockCmd(argv) {
12989
13279
 
12990
13280
  // src/bin-internal/validate-feature.ts
12991
13281
  init_paths2();
12992
- import { existsSync as existsSync30, readFileSync as readFileSync26 } from "fs";
13282
+ import { existsSync as existsSync32, readFileSync as readFileSync28 } from "fs";
12993
13283
  import { validateGherkin as validateGherkin2 } from "@xera-ai/web";
12994
13284
  async function validateFeatureCmd(argv) {
12995
13285
  const ticket = argv[0];
@@ -12998,11 +13288,11 @@ async function validateFeatureCmd(argv) {
12998
13288
  return 1;
12999
13289
  }
13000
13290
  const paths = resolveArtifactPaths(process.cwd(), ticket);
13001
- if (!existsSync30(paths.featurePath)) {
13291
+ if (!existsSync32(paths.featurePath)) {
13002
13292
  console.error(`[xera:validate-feature] missing ${paths.featurePath}`);
13003
13293
  return 1;
13004
13294
  }
13005
- const r = validateGherkin2(readFileSync26(paths.featurePath, "utf8"));
13295
+ const r = validateGherkin2(readFileSync28(paths.featurePath, "utf8"));
13006
13296
  if (r.ok) {
13007
13297
  console.log("[xera:validate-feature] ok");
13008
13298
  return 0;
@@ -13024,6 +13314,8 @@ var COMMANDS = {
13024
13314
  "eval-prepare": evalPrepareCmd,
13025
13315
  "eval-report": evalReportCmd,
13026
13316
  exec: execCmd,
13317
+ "explore-finalize": exploreFinalizeCmd,
13318
+ "explore-prepare": explorePrepareCmd,
13027
13319
  "fill-gap-finalize": fillGapFinalizeCmd,
13028
13320
  "fill-gap-prepare": fillGapPrepareCmd,
13029
13321
  fetch: fetchCmd,
@@ -13062,7 +13354,11 @@ Commands: ${Object.keys(COMMANDS).join(", ")}`);
13062
13354
  }
13063
13355
 
13064
13356
  // bin/internal.ts
13065
- import_dotenv.config({ path: ".env.local" });
13357
+ if (existsSync33(".env.local")) {
13358
+ console.error(`
13359
+ warning: .env.local detected but ignored. xera uses .env only \u2014 ` + `merge values from .env.local into .env and delete .env.local to silence this warning.
13360
+ `);
13361
+ }
13066
13362
  import_dotenv.config();
13067
13363
  var code = await run(process.argv.slice(2));
13068
13364
  process.exit(code);